or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

content-parsing.mddecoration.mderror-handling.mdhooks.mdindex.mdplugins.mdrouting.mdschema.mdserver-lifecycle.mdtesting.md
tile.json

decoration.mddocs/

Decoration System

Extend Fastify instances, requests, and replies with custom properties and methods.

Capabilities

Instance Decoration

Add properties and methods to the Fastify instance.

/**
 * Add property or method to Fastify instance
 * @param name - Property name
 * @param value - Property value or method function
 * @param dependencies - Array of required decorators
 * @returns FastifyInstance for method chaining
 */
decorate(name: string, value: any, dependencies?: string[]): FastifyInstance;

/**
 * Check if decorator exists on instance
 * @param name - Decorator name to check
 * @returns Boolean indicating if decorator exists
 */
hasDecorator(name: string): boolean;

/**
 * Get decorator value from instance
 * @param name - Decorator name
 * @returns Decorator value
 */
getDecorator(name: string): any;

Usage Examples:

// Add utility method
fastify.decorate('utils', {
  formatDate: (date) => date.toISOString(),
  generateId: () => Math.random().toString(36).substr(2, 9),
  validateEmail: (email) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)
});

// Use decorated utility
fastify.get('/current-time', async (request, reply) => {
  return {
    time: fastify.utils.formatDate(new Date()),
    id: fastify.utils.generateId()
  };
});

// Add database connection
fastify.decorate('db', database);

// Use in routes
fastify.get('/users', async (request, reply) => {
  const users = await fastify.db.query('SELECT * FROM users');
  return users;
});

Request Decoration

Add properties and methods to request objects.

/**
 * Add property or method to request objects
 * @param name - Property name
 * @param value - Property value or method function
 * @param dependencies - Array of required decorators
 * @returns FastifyInstance for method chaining
 */
decorateRequest(name: string, value: any, dependencies?: string[]): FastifyInstance;

/**
 * Check if request decorator exists
 * @param name - Decorator name to check
 * @returns Boolean indicating if decorator exists
 */
hasRequestDecorator(name: string): boolean;

Usage Examples:

// Add user context to requests
fastify.decorateRequest('user', null);
fastify.decorateRequest('getUserRole', function() {
  return this.user?.role || 'guest';
});

// Use in hooks
fastify.addHook('preHandler', async (request, reply) => {
  const token = request.headers.authorization?.replace('Bearer ', '');
  if (token) {
    request.user = await verifyToken(token);
  }
});

// Use in route handlers
fastify.get('/profile', async (request, reply) => {
  if (!request.user) {
    reply.code(401).send({ error: 'Unauthorized' });
    return;
  }
  
  return {
    user: request.user,
    role: request.getUserRole()
  };
});

// Add request tracking
fastify.decorateRequest('startTime', null);
fastify.decorateRequest('getElapsed', function() {
  return Date.now() - this.startTime;
});

fastify.addHook('onRequest', (request, reply, done) => {
  request.startTime = Date.now();
  done();
});

Reply Decoration

Add properties and methods to reply objects.

/**
 * Add property or method to reply objects
 * @param name - Property name
 * @param value - Property value or method function
 * @param dependencies - Array of required decorators
 * @returns FastifyInstance for method chaining
 */
decorateReply(name: string, value: any, dependencies?: string[]): FastifyInstance;

/**
 * Check if reply decorator exists  
 * @param name - Decorator name to check
 * @returns Boolean indicating if decorator exists
 */
hasReplyDecorator(name: string): boolean;

Usage Examples:

// Add success response helper
fastify.decorateReply('success', function(data, message = 'Success') {
  return this.send({
    success: true,
    message,
    data,
    timestamp: new Date().toISOString()
  });
});

// Add error response helper
fastify.decorateReply('error', function(message, code = 400) {
  return this.code(code).send({
    success: false,
    error: message,
    timestamp: new Date().toISOString()
  });
});

// Use in route handlers
fastify.get('/users/:id', async (request, reply) => {
  try {
    const user = await findUser(request.params.id);
    if (!user) {
      return reply.error('User not found', 404);
    }
    return reply.success(user, 'User retrieved successfully');
  } catch (err) {
    return reply.error('Failed to retrieve user', 500);
  }
});

// Add pagination helper
fastify.decorateReply('paginated', function(data, page, limit, total) {
  return this.send({
    data,
    pagination: {
      page,
      limit,
      total,
      pages: Math.ceil(total / limit)
    }
  });
});

Decoration Dependencies

Manage decorator dependencies and load order.

// Decorator with dependencies
fastify.decorate('config', appConfig);
fastify.decorate('database', databaseConnection, ['config']);
fastify.decorate('cache', cacheInstance, ['config', 'database']);

// Fastify ensures decorators are available in the right order
fastify.get('/status', async (request, reply) => {
  return {
    database: await fastify.database.status(),
    cache: await fastify.cache.status(),
    config: fastify.config.environment
  };
});

// Plugin with dependencies
async function databasePlugin(fastify, options) {
  // This plugin requires 'config' decorator
  if (!fastify.hasDecorator('config')) {
    throw new Error('Config decorator required');
  }
  
  fastify.decorate('db', createDatabase(fastify.config.database));
}

// Register with dependencies
await fastify.register(configPlugin);
await fastify.register(databasePlugin); // Will have config available

Getter/Setter Decorators

Create decorators with getter and setter functions.

// Decorator with getter/setter
fastify.decorate('currentUser', {
  getter() {
    return this.user || null;
  },
  setter(user) {
    this.user = user;
    this.log.info({ userId: user.id }, 'User context set');
  }
});

// Usage in middleware
fastify.addHook('preHandler', async (request, reply) => {
  const token = request.headers.authorization;
  if (token) {
    const user = await validateToken(token);
    fastify.currentUser = user; // Uses setter
  }
});

// Usage in routes
fastify.get('/me', async (request, reply) => {
  const user = fastify.currentUser; // Uses getter
  if (!user) {
    reply.code(401).send({ error: 'Not authenticated' });
    return;
  }
  return user;
});

Plugin-scoped Decorations

Decorators are scoped to plugin contexts for encapsulation.

// Parent context
fastify.decorate('parentHelper', () => 'from parent');

await fastify.register(async function childPlugin(fastify) {
  // Child has access to parent decorators
  console.log(fastify.parentHelper()); // "from parent"
  
  // Child-specific decorator
  fastify.decorate('childHelper', () => 'from child');
  
  fastify.get('/child-route', (request, reply) => {
    return {
      parent: fastify.parentHelper(),
      child: fastify.childHelper()
    };
  });
});

// Parent cannot access child decorators
// console.log(fastify.childHelper()); // Error!

Advanced Decoration Patterns

Complex decoration scenarios and best practices.

// Service container pattern
fastify.decorate('services', {});

// Register services
fastify.services.user = {
  async findById(id) {
    return await userRepository.findById(id);
  },
  async create(data) {
    return await userRepository.create(data);
  }
};

fastify.services.email = {
  async send(to, subject, body) {
    return await emailProvider.send(to, subject, body);
  }
};

// Factory pattern for decorators
function createApiDecorator(baseUrl, headers = {}) {
  return {
    async get(path) {
      const response = await fetch(`${baseUrl}${path}`, { headers });
      return response.json();
    },
    async post(path, data) {
      const response = await fetch(`${baseUrl}${path}`, {
        method: 'POST',
        headers: { ...headers, 'Content-Type': 'application/json' },
        body: JSON.stringify(data)
      });
      return response.json();
    }
  };
}

// Use factory
fastify.decorate('api', createApiDecorator('https://api.example.com', {
  'Authorization': 'Bearer token'
}));

// Conditional decorations
if (process.env.NODE_ENV === 'development') {
  fastify.decorate('debug', {
    logRequest: (request) => console.log(request.method, request.url),
    logResponse: (reply) => console.log('Response:', reply.statusCode)
  });
} else {
  fastify.decorate('debug', {
    logRequest: () => {},
    logResponse: () => {}
  });
}

Decoration Type Safety (TypeScript)

Extend types for decorated properties.

// Extend Fastify types
declare module 'fastify' {
  interface FastifyInstance {
    config: AppConfig;
    db: Database;
    services: ServiceContainer;
  }
  
  interface FastifyRequest {
    user?: User;
    startTime: number;
    getUserRole(): string;
  }
  
  interface FastifyReply {
    success(data: any, message?: string): FastifyReply;
    error(message: string, code?: number): FastifyReply;
  }
}

// Use with full type safety
fastify.get('/users', async (request, reply) => {
  const users = await fastify.db.users.findAll(); // Typed
  return reply.success(users); // Typed
});