or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

client-operations.mdconstants.mdcredentials.mdindex.mdinterceptors.mdmetadata.mdserver-operations.mdservice-definitions.md
tile.json

interceptors.mddocs/

Interceptors

Request/response interception framework for implementing cross-cutting concerns like logging, authentication, metrics, and error handling on both client and server sides with flexible composition patterns.

Capabilities

Client Interceptors

Client-side interception system for modifying outgoing requests and incoming responses.

/**
 * Client interceptor function interface
 * @param options - Interceptor configuration options
 * @param nextCall - Function to continue the interceptor chain
 * @returns InterceptingCall instance for handling the call
 */
interface Interceptor {
  (options: InterceptorOptions, nextCall: NextCall): InterceptingCall;
}

/**
 * Options passed to client interceptors
 */
interface InterceptorOptions {
  /** The method being called */
  method_definition: MethodDefinition<any, any>;
}

/**
 * Function to continue the interceptor chain
 */
interface NextCall {
  (options: InterceptorOptions): InterceptingCall;
}

/**
 * Intercepting call interface for handling requests and responses
 */
interface InterceptingCall {
  /** Start the call with metadata and listener */
  start(metadata: Metadata, listener: InterceptingListener, next: Function): void;
  /** Send a message (for streaming calls) */
  sendMessage(message: any, next: Function): void;
  /** Half-close the call (signal end of client messages) */
  halfClose(next: Function): void;
  /** Cancel the call */
  cancel(next: Function): void;
}

Client Interceptor Providers

Dynamic interceptor creation for context-dependent interception.

/**
 * Provider for creating interceptors dynamically
 */
interface InterceptorProvider {
  (methodDefinition: MethodDefinition<any, any>): Interceptor;
}

/**
 * Error thrown when interceptor configuration is invalid
 */
class InterceptorConfigurationError extends Error {
  constructor(message: string);
}

Client Interceptor Builders

Helper classes for building complex interceptors with fluent API.

/**
 * Builder for request interceptors
 */
class RequesterBuilder {
  /** Build a requester function */
  build(): Requester;
}

/**
 * Builder for response listeners
 */
class ListenerBuilder {
  /** Build a listener function */
  build(): InterceptingListener;
}

/**
 * Request interceptor interface
 */
interface Requester {
  /** Start the request */
  start(metadata: Metadata, listener: InterceptingListener, next: Function): void;
  /** Send message */
  sendMessage(message: any, next: Function): void;
  /** Half close */
  halfClose(next: Function): void;
  /** Cancel */
  cancel(next: Function): void;
}

Server Interceptors

Server-side interception system for processing incoming requests and outgoing responses.

/**
 * Server interceptor function interface
 * @param methodDescriptor - Description of the method being called
 * @param call - The incoming server call
 * @returns Modified or wrapped server intercepting call
 */
interface ServerInterceptor {
  <ReqType, RespType>(
    methodDescriptor: ServerMethodDefinition<ReqType, RespType>,
    call: ServerInterceptingCallInterface<ReqType>
  ): ServerInterceptingCall<ReqType, RespType>;
}

Server Interceptor Interfaces

Interfaces for server-side call interception and response handling.

/**
 * Server intercepting call interface
 */
interface ServerInterceptingCallInterface<RequestType> {
  /** Request metadata */
  metadata: Metadata;
  /** Get peer address */
  getPeer(): string;
  /** Send initial metadata */
  sendMetadata(responseMetadata: Metadata): void;
  /** Check if cancelled */
  isCancelled(): boolean;
}

/**
 * Server intercepting call implementation
 */
interface ServerInterceptingCall<RequestType, ResponseType> 
  extends ServerInterceptingCallInterface<RequestType> {
  /** Request data (for unary calls) */
  request?: RequestType;
}

Server Listeners and Responders

Components for handling server-side request processing and response generation.

/**
 * Server listener interface for processing requests
 */
interface ServerListener {
  /** Called when metadata is received */
  onReceiveMetadata(metadata: Metadata): void;
  /** Called when a message is received */
  onReceiveMessage(message: any): void;
  /** Called when client half-closes */
  onReceiveHalfClose(): void;
  /** Called when call is cancelled */
  onCancel(): void;
}

/**
 * Full server listener with additional events
 */
interface FullServerListener extends ServerListener {
  /** Called when call starts */
  onReceiveCall(call: any): void;
}

/**
 * Server responder interface for sending responses
 */
interface Responder {
  /** Send initial metadata */
  sendMetadata(metadata: Metadata): void;
  /** Send a message */
  sendMessage(message: any): void;
  /** Send status and close */
  sendStatus(status: StatusObject): void;
}

/**
 * Full server responder with additional capabilities
 */
interface FullResponder extends Responder {
  /** Start the response */
  start(metadata: Metadata): void;
}

/**
 * Builder for server listeners
 */
class ServerListenerBuilder {
  /** Build a listener */
  build(): ServerListener;
}

/**
 * Builder for server responders
 */
class ResponderBuilder {
  /** Build a responder */
  build(): Responder;
}

Call Interface Types

Core interfaces for call listeners and intercepting listeners.

/**
 * Basic call listener interface
 */
interface Listener {
  /** Called when metadata is received */
  onReceiveMetadata(metadata: Metadata): void;
  /** Called when a message is received */
  onReceiveMessage(message: any): void;
  /** Called when status is received */
  onReceiveStatus(status: StatusObject): void;
}

/**
 * Intercepting listener interface with additional events
 */
interface InterceptingListener extends Listener {
  /** Called when initial metadata is received */
  onReceiveInitialMetadata(metadata: Metadata): void;
  /** Called when trailing metadata is received */
  onReceiveTrailingMetadata(metadata: Metadata): void;
}

Usage Examples

Basic Client Interceptor for Authentication

import { Interceptor, Metadata } from "@grpc/grpc-js";

const authInterceptor: Interceptor = (options, nextCall) => {
  return new InterceptingCall(nextCall(options), {
    start(metadata, listener, next) {
      // Add authentication to all outgoing calls
      metadata.set('authorization', `Bearer ${getAuthToken()}`);
      metadata.set('x-request-id', generateRequestId());
      next(metadata, listener);
    }
  });
};

// Use with client
const client = new ServiceClient(address, credentials.createInsecure(), {
  interceptors: [authInterceptor]
});

Client Interceptor for Logging

import { Interceptor, InterceptingCall } from "@grpc/grpc-js";

const loggingInterceptor: Interceptor = (options, nextCall) => {
  const methodName = options.method_definition.path;
  const startTime = Date.now();
  
  return new InterceptingCall(nextCall(options), {
    start(metadata, listener, next) {
      console.log(`[${methodName}] Call started`);
      console.log(`[${methodName}] Metadata:`, metadata.getMap());
      
      const wrappedListener = {
        onReceiveInitialMetadata(metadata) {
          console.log(`[${methodName}] Received initial metadata`);
          listener.onReceiveInitialMetadata(metadata);
        },
        
        onReceiveMessage(message) {
          console.log(`[${methodName}] Received message:`, message);
          listener.onReceiveMessage(message);
        },
        
        onReceiveStatus(status) {
          const duration = Date.now() - startTime;
          console.log(`[${methodName}] Call completed in ${duration}ms with status:`, status.code);
          listener.onReceiveStatus(status);
        }
      };
      
      next(metadata, wrappedListener);
    },
    
    sendMessage(message, next) {
      console.log(`[${methodName}] Sending message:`, message);
      next(message);
    }
  });
};

Client Interceptor for Retry Logic

import { Interceptor, status } from "@grpc/grpc-js";

const retryInterceptor: Interceptor = (options, nextCall) => {
  const maxRetries = 3;
  let attemptCount = 0;
  
  const makeCall = (): InterceptingCall => {
    attemptCount++;
    
    return new InterceptingCall(nextCall(options), {
      start(metadata, listener, next) {
        const wrappedListener = {
          ...listener,
          onReceiveStatus(statusObj) {
            if (shouldRetry(statusObj.code) && attemptCount < maxRetries) {
              console.log(`Retrying call, attempt ${attemptCount + 1}`);
              // Start a new call
              const retryCall = makeCall();
              retryCall.start(metadata, listener, () => {});
              return;
            }
            listener.onReceiveStatus(statusObj);
          }
        };
        
        next(metadata, wrappedListener);
      }
    });
  };
  
  return makeCall();
};

function shouldRetry(statusCode: status): boolean {
  return statusCode === status.UNAVAILABLE || 
         statusCode === status.DEADLINE_EXCEEDED ||
         statusCode === status.RESOURCE_EXHAUSTED;
}

Server Interceptor for Authentication

import { ServerInterceptor, status, Metadata } from "@grpc/grpc-js";

const authServerInterceptor: ServerInterceptor = (methodDescriptor, call) => {
  const metadata = call.metadata;
  const authHeader = metadata.get('authorization')[0] as string;
  
  if (!authHeader || !authHeader.startsWith('Bearer ')) {
    // Send authentication error
    const errorMetadata = new Metadata();
    errorMetadata.set('www-authenticate', 'Bearer');
    
    throw {
      code: status.UNAUTHENTICATED,
      details: 'Missing or invalid authentication token',
      metadata: errorMetadata
    };
  }
  
  const token = authHeader.substring(7);
  if (!validateToken(token)) {
    throw {
      code: status.PERMISSION_DENIED,
      details: 'Invalid authentication token'
    };
  }
  
  // Add user info to metadata for handlers
  metadata.set('x-user-id', getUserIdFromToken(token));
  
  return call; // Continue with authenticated call
};

function validateToken(token: string): boolean {
  // Token validation logic
  return token.length > 0;
}

function getUserIdFromToken(token: string): string {
  // Extract user ID from token
  return 'user123';
}

Server Interceptor for Request Logging

import { ServerInterceptor } from "@grpc/grpc-js";

const requestLoggingInterceptor: ServerInterceptor = (methodDescriptor, call) => {
  const startTime = Date.now();
  const methodName = methodDescriptor.path;
  const clientAddress = call.getPeer();
  const requestId = call.metadata.get('x-request-id')[0] || 'unknown';
  
  console.log(`[${requestId}] ${methodName} started from ${clientAddress}`);
  console.log(`[${requestId}] Request metadata:`, call.metadata.getMap());
  
  // Wrap the call to log completion
  const originalCall = call;
  
  return new Proxy(call, {
    get(target, prop) {
      if (prop === 'sendMetadata') {
        return (metadata: Metadata) => {
          console.log(`[${requestId}] Sending initial metadata`);
          return originalCall.sendMetadata(metadata);
        };
      }
      
      return target[prop];
    }
  });
};

Multiple Interceptors Composition

import { Interceptor, ServerInterceptor } from "@grpc/grpc-js";

// Client interceptors (applied in order)
const clientInterceptors: Interceptor[] = [
  authInterceptor,
  loggingInterceptor,
  retryInterceptor,
  metricsInterceptor
];

// Server interceptors (applied in reverse order)
const serverInterceptors: ServerInterceptor[] = [
  requestLoggingInterceptor,
  authServerInterceptor,
  rateLimitingInterceptor,
  metricsServerInterceptor
];

// Client with interceptors
const client = new ServiceClient(address, credentials.createInsecure(), {
  interceptors: clientInterceptors
});

// Server with interceptors
const server = new Server({
  interceptors: serverInterceptors
});

Interceptor Provider Pattern

import { InterceptorProvider, MethodDefinition } from "@grpc/grpc-js";

const conditionalInterceptorProvider: InterceptorProvider = (methodDefinition) => {
  // Return different interceptors based on method
  if (methodDefinition.path.includes('Admin')) {
    return adminAuthInterceptor;
  } else if (methodDefinition.path.includes('Public')) {
    return publicLoggingInterceptor;
  } else {
    return standardInterceptor;
  }
};

// Use provider with client
const client = new ServiceClient(address, credentials.createInsecure(), {
  interceptor_providers: [conditionalInterceptorProvider]
});

Advanced Interceptor with State

import { Interceptor } from "@grpc/grpc-js";

class CircuitBreakerInterceptor {
  private failureCount = 0;
  private lastFailureTime = 0;
  private readonly maxFailures = 5;
  private readonly timeout = 60000; // 1 minute
  
  createInterceptor(): Interceptor {
    return (options, nextCall) => {
      // Check circuit breaker state
      if (this.isCircuitOpen()) {
        return new InterceptingCall(nextCall(options), {
          start(metadata, listener, next) {
            listener.onReceiveStatus({
              code: status.UNAVAILABLE,
              details: 'Circuit breaker is open',
              metadata: new Metadata()
            });
          }
        });
      }
      
      return new InterceptingCall(nextCall(options), {
        start: (metadata, listener, next) => {
          const wrappedListener = {
            ...listener,
            onReceiveStatus: (statusObj) => {
              if (statusObj.code !== status.OK) {
                this.recordFailure();
              } else {
                this.recordSuccess();
              }
              listener.onReceiveStatus(statusObj);
            }
          };
          
          next(metadata, wrappedListener);
        }
      });
    };
  }
  
  private isCircuitOpen(): boolean {
    const now = Date.now();
    return this.failureCount >= this.maxFailures && 
           (now - this.lastFailureTime) < this.timeout;
  }
  
  private recordFailure(): void {
    this.failureCount++;
    this.lastFailureTime = Date.now();
  }
  
  private recordSuccess(): void {
    this.failureCount = 0;
  }
}

const circuitBreaker = new CircuitBreakerInterceptor();
const client = new ServiceClient(address, credentials.createInsecure(), {
  interceptors: [circuitBreaker.createInterceptor()]
});