or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

application.mdhooks.mdindex.mdservice-interfaces.mdservices.md
tile.json

hooks.mddocs/

Hook System

Middleware system for intercepting and modifying service method calls with support for before, after, error, and around hooks.

Capabilities

Application Hooks

Register hooks that run for all services in the application.

/**
 * Register application level hooks
 * @param map - The application hook settings
 * @returns The application instance for chaining
 */
hooks(map: ApplicationHookOptions<this>): this;

type ApplicationHookOptions<A> = HookOptions<A, any> | ApplicationHookMap<A>;

interface ApplicationHookMap<A> {
  setup?: ApplicationHookFunction<A>[];
  teardown?: ApplicationHookFunction<A>[];
}

type ApplicationHookFunction<A> = (
  context: ApplicationHookContext<A>,
  next: NextFunction
) => Promise<void>;

interface ApplicationHookContext<A = Application> {
  app: A;
  server: any;
}

Usage Examples:

// Application-wide hooks for all services
app.hooks({
  before: {
    all: [
      async (context) => {
        console.log(`Calling ${context.method} on ${context.path}`);
      }
    ],
    create: [
      async (context) => {
        context.data.createdAt = new Date();
      }
    ]
  },
  after: {
    all: [
      async (context) => {
        console.log(`Finished ${context.method} on ${context.path}`);
      }
    ]
  }
});

// Application lifecycle hooks
app.hooks({
  setup: [
    async (context) => {
      console.log("Application is setting up");
    }
  ],
  teardown: [
    async (context) => {
      console.log("Application is tearing down");
    }
  ]
});

Service Hooks

Register hooks for specific services.

/**
 * Register service-level hooks
 * @param options - Hook configuration
 * @returns The service instance for chaining
 */
hooks(options: HookOptions<A, S>): this;

type HookOptions<A, S> = AroundHookMap<A, S> | AroundHookFunction<A, S>[] | HookMap<A, S>;

interface HookMap<A, S> {
  around?: AroundHookMap<A, S>;
  before?: HookTypeMap<A, S>;
  after?: HookTypeMap<A, S>;
  error?: HookTypeMap<A, S>;
}

type HookTypeMap<A, S> = SelfOrArray<HookFunction<A, S>> | HookMethodMap<A, S>;

type HookMethodMap<A, S> = {
  [L in keyof S]?: SelfOrArray<HookFunction<A, S>>;
} & { all?: SelfOrArray<HookFunction<A, S>> };

Usage Examples:

const messageService = app.service("messages");

// Service hooks using different hook types
messageService.hooks({
  before: {
    all: [
      async (context) => {
        // Runs before all methods
        console.log(`Before ${context.method}`);
      }
    ],
    create: [
      async (context) => {
        // Validate data before creating
        if (!context.data.text) {
          throw new Error("Text is required");
        }
        context.data.createdAt = new Date();
      }
    ]
  },
  after: {
    create: [
      async (context) => {
        // Send notification after creating
        console.log("Message created:", context.result);
      }
    ]
  },
  error: {
    all: [
      async (context) => {
        // Log all errors
        console.error(`Error in ${context.method}:`, context.error);
      }
    ]
  }
});

// Around hooks for more control
messageService.hooks({
  around: {
    all: [
      async (context, next) => {
        const start = Date.now();
        await next();
        const duration = Date.now() - start;
        console.log(`${context.method} took ${duration}ms`);
      }
    ]
  }
});

Hook Types

Hook Function Types

Different types of hook functions for various use cases.

/**
 * Regular hook function that can modify context
 */
type HookFunction<A = Application, S = Service> = (
  this: S,
  context: HookContext<A, S>
) => Promise<HookContext<Application, S> | void> | HookContext<Application, S> | void;

/**
 * Around hook function that controls execution flow
 */
type AroundHookFunction<A = Application, S = Service> = (
  context: HookContext<A, S>,
  next: NextFunction
) => Promise<void>;

/**
 * Alias for HookFunction
 */
type Hook<A = Application, S = Service> = HookFunction<A, S>;

/**
 * Hook types available
 */
type HookType = 'before' | 'after' | 'error' | 'around';

Hook Context

The context object passed to all hooks containing request and response data.

interface HookContext<A = Application, S = any> {
  /**
   * A read only property that contains the Feathers application object
   */
  readonly app: A;
  
  /**
   * A read only property with the name of the service method
   */
  readonly method: string;
  
  /**
   * A read only property and contains the service name (or path)
   */
  path: string;
  
  /**
   * A read only property and contains the service this hook currently runs on
   */
  readonly service: S;
  
  /**
   * A read only property with the hook type
   */
  readonly type: HookType;
  
  /**
   * The list of method arguments. Should not be modified
   */
  readonly arguments: any[];
  
  /**
   * A writeable property containing the data of a create, update and patch service method call
   */
  data?: any;
  
  /**
   * A writeable property with the error object that was thrown in a failed method call
   */
  error?: any;
  
  /**
   * A writeable property and the id for a get, remove, update and patch service method call
   */
  id?: Id;
  
  /**
   * A writeable property that contains the service method parameters
   */
  params: Params;
  
  /**
   * A writeable property containing the result of the successful service method call
   */
  result?: any;
  
  /**
   * A writeable property containing a 'safe' version of the data that should be sent to any client
   */
  dispatch?: any;
  
  /**
   * A writeable, optional property with options specific to HTTP transports
   */
  http?: Http;
  
  /**
   * The event emitted by this method. Can be set to null to skip event emitting
   */
  event: string | null;
}

Usage Examples:

// Before hook modifying data
const validateAndEnhance = async (context: HookContext) => {
  if (context.method === "create") {
    // Validate required fields
    if (!context.data.title) {
      throw new Error("Title is required");
    }
    
    // Add timestamps
    context.data.createdAt = new Date();
    context.data.updatedAt = new Date();
    
    // Add user info from params
    if (context.params.user) {
      context.data.userId = context.params.user.id;
    }
  }
};

// After hook modifying result
const addComputedFields = async (context: HookContext) => {
  if (context.result) {
    const results = Array.isArray(context.result) ? context.result : [context.result];
    
    results.forEach(item => {
      if (item.createdAt) {
        item.age = Date.now() - new Date(item.createdAt).getTime();
      }
    });
  }
};

// Error hook for logging
const logErrors = async (context: HookContext) => {
  console.error(`Error in ${context.path}.${context.method}:`, {
    error: context.error.message,
    data: context.data,
    params: context.params
  });
};

// Around hook for caching
const cacheResults = async (context: HookContext, next: NextFunction) => {
  if (context.method === "find") {
    const cacheKey = `${context.path}-${JSON.stringify(context.params.query)}`;
    const cached = cache.get(cacheKey);
    
    if (cached) {
      context.result = cached;
      return; // Skip calling next()
    }
    
    await next();
    
    if (context.result) {
      cache.set(cacheKey, context.result, 60000); // Cache for 1 minute
    }
  } else {
    await next();
  }
};

Hook Utilities

Hook Management

Utilities for managing and creating hooks.

/**
 * Create a hook context for a service method
 * @param service - The service instance
 * @param method - The method name
 * @param data - Optional context data
 * @returns Hook context object
 */
function createContext(service: Service, method: string, data: HookContextData = {}): HookContext;

/**
 * Enable hooks functionality on an object
 * @param object - The object to enhance with hooks
 * @returns Function to register hooks
 */
function enableHooks(object: any): Function;

/**
 * Hook manager class for handling hook execution
 */
class FeathersHookManager<A> extends HookManager {
  constructor(app: A, method: string);
  collectMiddleware(self: any, args: any[]): Middleware[];
  initializeContext(self: any, args: any[], context: HookContext): HookContext;
  middleware(mw: Middleware[]): this;
}

/**
 * Mixin to add hook functionality to services
 */
function hookMixin<A>(
  this: A,
  service: FeathersService<A>,
  path: string,
  options: ServiceOptions
): FeathersService<A>;

Hook Registration Patterns

// Method-specific hooks
messageService.hooks({
  before: {
    find: [hookFunction1, hookFunction2],
    create: [validateData, addTimestamp]
  }
});

// All methods hooks
messageService.hooks({
  before: {
    all: [authenticate, authorize]
  }
});

// Multiple hook types
messageService.hooks({
  before: {
    create: [validateData]
  },
  after: {
    create: [sendNotification]
  },
  error: {
    all: [logError]
  }
});

// Around hooks only
messageService.hooks([
  cacheHook,
  timingHook
]);

// Mixed registration
messageService.hooks({
  around: {
    find: [cacheHook],
    all: [timingHook]
  },
  before: {
    create: [validateData]
  }
});

Common Hook Patterns

Authentication Hook:

const authenticate = async (context: HookContext) => {
  const token = context.params.headers?.authorization;
  if (!token) {
    throw new Error("Authentication required");
  }
  
  context.params.user = await verifyToken(token);
};

Validation Hook:

const validateSchema = (schema: any) => async (context: HookContext) => {
  if (context.data) {
    const validation = await schema.validate(context.data);
    if (!validation.isValid) {
      throw new Error(`Validation failed: ${validation.errors.join(", ")}`);
    }
  }
};

Pagination Hook:

const paginate = async (context: HookContext) => {
  if (context.method === "find") {
    const { $limit = 10, $skip = 0 } = context.params.query || {};
    context.params.query = {
      ...context.params.query,
      $limit: Math.min($limit, 100), // Max 100 items
      $skip
    };
  }
};