CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-grpc--grpc-js

Pure JavaScript gRPC implementation for Node.js with comprehensive client-server communication capabilities

Pending
Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

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()]
});

docs

client-operations.md

constants.md

credentials.md

index.md

interceptors.md

metadata.md

server-operations.md

service-definitions.md

tile.json