CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-protobufjs

Protocol Buffers for JavaScript with TypeScript support, providing pure JavaScript implementation for serializing and deserializing structured data using Google's Protocol Buffer format.

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

Overview
Eval results
Files

rpc-services.mddocs/

RPC Services

Built-in RPC service framework for implementing and consuming protobuf-based services with support for both traditional and streaming RPC patterns.

Capabilities

Service Definition

Service reflection and definition management for RPC services.

class Service extends Namespace {
  /**
   * Constructs a new service
   * @param name - Service name
   * @param options - Service options
   */
  constructor(name: string, options?: object);
  
  /**
   * Creates service from JSON descriptor
   * @param name - Service name
   * @param json - JSON service descriptor
   * @returns Service instance
   */
  static fromJSON(name: string, json: object): Service;
  
  /**
   * Service methods by name
   */
  methods: { [name: string]: Method };
  
  /**
   * Array of all methods
   */
  methodsArray: Method[];
  
  /**
   * Creates RPC service instance
   * @param rpcImpl - RPC implementation function
   * @param requestDelimited - Whether requests are length-delimited
   * @param responseDelimited - Whether responses are length-delimited
   * @returns RPC service instance
   */
  create(rpcImpl: RPCImpl, requestDelimited?: boolean, responseDelimited?: boolean): rpc.Service;
  
  /**
   * Adds method to service
   * @param method - Method to add
   * @returns This service
   */
  add(method: Method): Service;
  
  /**
   * Removes method from service
   * @param method - Method to remove
   * @returns This service
   */
  remove(method: Method): Service;
}

Usage Examples:

const protobuf = require("protobufjs");

protobuf.load("service.proto", function(err, root) {
    const UserService = root.lookupService("UserService");
    
    console.log("Service methods:");
    UserService.methodsArray.forEach(method => {
        console.log(`${method.name}: ${method.requestType} -> ${method.responseType}`);
    });
    
    // Create service instance
    const service = UserService.create(rpcImplementation);
});

Method Definition

RPC method reflection and metadata.

class Method extends ReflectionObject {
  /**
   * Constructs a new method
   * @param name - Method name
   * @param type - Method type (typically "rpc")
   * @param requestType - Request message type name
   * @param responseType - Response message type name
   * @param requestStream - Whether request is streaming
   * @param responseStream - Whether response is streaming
   * @param options - Method options
   */
  constructor(name: string, type: string, requestType: string, responseType: string, 
             requestStream?: boolean, responseStream?: boolean, options?: object);
  
  /**
   * Method type (typically "rpc")
   */
  type: string;
  
  /**
   * Request message type name
   */
  requestType: string;
  
  /**
   * Response message type name
   */
  responseType: string;
  
  /**
   * Whether request is streaming
   */
  requestStream: boolean;
  
  /**
   * Whether response is streaming
   */
  responseStream: boolean;
  
  /**
   * Resolved request message type
   */
  resolvedRequestType: Type | null;
  
  /**
   * Resolved response message type
   */
  resolvedResponseType: Type | null;
}

Usage Examples:

// Inspect method details
const getUserMethod = UserService.methods["getUser"];
console.log("Method:", getUserMethod.name);
console.log("Request type:", getUserMethod.requestType);
console.log("Response type:", getUserMethod.responseType);
console.log("Streaming:", {
    request: getUserMethod.requestStream,
    response: getUserMethod.responseStream
});

// Access resolved types
const RequestType = getUserMethod.resolvedRequestType;
const ResponseType = getUserMethod.resolvedResponseType;

RPC Implementation Interface

Interface for implementing RPC transport and execution.

/**
 * RPC implementation function
 * @param method - Method being called
 * @param requestData - Encoded request data
 * @param callback - Callback for response
 */
interface RPCImpl {
  (method: Method, requestData: Uint8Array, callback: RPCImplCallback): void;
}

/**
 * RPC implementation callback
 * @param error - Error if call failed, null on success
 * @param response - Encoded response data if successful
 */
interface RPCImplCallback {
  (error: Error | null, response?: Uint8Array): void;
}

Usage Examples:

// HTTP-based RPC implementation
function httpRpcImpl(method, requestData, callback) {
    const xhr = new XMLHttpRequest();
    xhr.open("POST", `/rpc/${method.name}`);
    xhr.responseType = "arraybuffer";
    
    xhr.onload = function() {
        if (xhr.status === 200) {
            const responseBuffer = new Uint8Array(xhr.response);
            callback(null, responseBuffer);
        } else {
            callback(new Error(`HTTP ${xhr.status}: ${xhr.statusText}`));
        }
    };
    
    xhr.onerror = function() {
        callback(new Error("Network error"));
    };
    
    xhr.send(requestData);
}

// WebSocket-based RPC implementation
function websocketRpcImpl(method, requestData, callback) {
    const message = {
        method: method.name,
        data: Array.from(requestData)
    };
    
    websocket.send(JSON.stringify(message));
    
    // Store callback for response matching
    pendingCalls[generateId()] = callback;
}

// Node.js HTTP client implementation
function nodeHttpRpcImpl(method, requestData, callback) {
    const options = {
        hostname: 'localhost',
        port: 8080,
        path: `/rpc/${method.name}`,
        method: 'POST',
        headers: {
            'Content-Type': 'application/x-protobuf',
            'Content-Length': requestData.length
        }
    };
    
    const req = require('http').request(options, (res) => {
        const chunks = [];
        res.on('data', chunk => chunks.push(chunk));
        res.on('end', () => {
            const responseBuffer = Buffer.concat(chunks);
            callback(null, new Uint8Array(responseBuffer));
        });
    });
    
    req.on('error', callback);
    req.write(requestData);
    req.end();
}

Service Instance

Runtime service instance for making RPC calls.

namespace rpc {
  class Service {
    /**
     * RPC implementation
     */
    rpcImpl: RPCImpl;
    
    /**
     * Whether requests are length-delimited
     */
    requestDelimited: boolean;
    
    /**
     * Whether responses are length-delimited
     */
    responseDelimited: boolean;
    
    /**
     * Creates service method wrapper
     * @param method - Method definition
     * @param requestCtor - Request message constructor
     * @param responseCtor - Response message constructor
     * @returns Method wrapper function
     */
    rpcCall<TRequest, TResponse>(
      method: Method,
      requestCtor: Constructor<TRequest>,
      responseCtor: Constructor<TResponse>
    ): (request: TRequest, callback: ServiceMethodCallback<TResponse>) => void;
  }
}

/**
 * Service method callback
 * @param error - Error if call failed
 * @param response - Response message if successful
 */
interface ServiceMethodCallback<TResponse> {
  (error: Error | null, response?: TResponse): void;
}

Usage Examples:

// Create and use service instance
const userService = UserService.create(httpRpcImpl);

// Make RPC calls
const getUserRequest = { userId: 123 };
userService.getUser(getUserRequest, function(err, response) {
    if (err) {
        console.error("RPC error:", err);
        return;
    }
    
    console.log("User:", response.name, response.email);
});

// Promise-based wrapper
function promisifyService(service) {
    const promisified = {};
    
    Object.keys(service).forEach(methodName => {
        if (typeof service[methodName] === 'function') {
            promisified[methodName] = function(request) {
                return new Promise((resolve, reject) => {
                    service[methodName](request, (err, response) => {
                        if (err) reject(err);
                        else resolve(response);
                    });
                });
            };
        }
    });
    
    return promisified;
}

// Use promisified service
const promisedUserService = promisifyService(userService);
promisedUserService.getUser({ userId: 123 })
    .then(user => console.log("User:", user))
    .catch(err => console.error("Error:", err));

Streaming RPC Support

Support for streaming RPC patterns (client streaming, server streaming, bidirectional streaming).

interface StreamingPatterns {
  /**
   * Unary RPC (single request, single response)
   */
  unary: {
    requestStream: false;
    responseStream: false;
  };
  
  /**
   * Client streaming (multiple requests, single response)
   */
  clientStreaming: {
    requestStream: true;
    responseStream: false;
  };
  
  /**
   * Server streaming (single request, multiple responses)
   */
  serverStreaming: {
    requestStream: false;
    responseStream: true;
  };
  
  /**
   * Bidirectional streaming (multiple requests, multiple responses)
   */
  bidirectionalStreaming: {
    requestStream: true;
    responseStream: true;
  };
}

Usage Examples:

// Server streaming implementation
function serverStreamingRpcImpl(method, requestData, callback) {
    if (method.responseStream) {
        // Set up streaming response handler
        const stream = new EventEmitter();
        
        stream.on('data', (responseData) => {
            // Each response chunk
            callback(null, responseData);
        });
        
        stream.on('end', () => {
            // Signal end of stream
            callback(null, null);
        });
        
        stream.on('error', (err) => {
            callback(err);
        });
        
        // Initiate streaming call
        initiateStreamingCall(method.name, requestData, stream);
    } else {
        // Regular unary call
        regularRpcImpl(method, requestData, callback);
    }
}

// Client streaming implementation
function clientStreamingService(method, requestStream) {
    return new Promise((resolve, reject) => {
        const chunks = [];
        
        requestStream.on('data', (requestData) => {
            chunks.push(requestData);
        });
        
        requestStream.on('end', () => {
            // Combine all request chunks
            const combinedRequest = Buffer.concat(chunks);
            
            // Make single RPC call with combined data
            rpcImpl(method, combinedRequest, (err, response) => {
                if (err) reject(err);
                else resolve(response);
            });
        });
    });
}

Service Registration

Utilities for registering and discovering RPC services.

interface ServiceRegistry {
  /**
   * Registers service implementation
   * @param serviceName - Service name
   * @param implementation - Service implementation
   */
  register(serviceName: string, implementation: object): void;
  
  /**
   * Gets registered service
   * @param serviceName - Service name
   * @returns Service implementation
   */
  get(serviceName: string): object | null;
  
  /**
   * Lists all registered services
   * @returns Array of service names
   */
  list(): string[];
}

Usage Examples:

// Service implementation
const userServiceImpl = {
    getUser: function(request, callback) {
        // Implementation logic
        const user = database.findUser(request.userId);
        const response = UserResponse.create({
            id: user.id,
            name: user.name,
            email: user.email
        });
        callback(null, response);
    },
    
    createUser: function(request, callback) {
        // Implementation logic
        const newUser = database.createUser(request);
        const response = UserResponse.create(newUser);
        callback(null, response);
    }
};

// Register service
serviceRegistry.register("UserService", userServiceImpl);

// Generic RPC handler
function handleRpcCall(serviceName, methodName, requestData, callback) {
    const service = serviceRegistry.get(serviceName);
    if (!service) {
        return callback(new Error(`Service not found: ${serviceName}`));
    }
    
    const method = service[methodName];
    if (!method) {
        return callback(new Error(`Method not found: ${methodName}`));
    }
    
    // Decode request and call method
    const ServiceDef = root.lookupService(serviceName);
    const MethodDef = ServiceDef.methods[methodName];
    const RequestType = MethodDef.resolvedRequestType;
    
    const request = RequestType.decode(requestData);
    method(request, callback);
}

Error Handling

Comprehensive error handling for RPC operations.

interface RPCError extends Error {
  name: string;           // Error type
  message: string;        // Error description
  code?: number;          // Error code
  details?: any;          // Additional error details
  method?: string;        // Method that failed
}

interface RPCErrorCodes {
  OK: 0;
  CANCELLED: 1;
  UNKNOWN: 2;
  INVALID_ARGUMENT: 3;
  DEADLINE_EXCEEDED: 4;
  NOT_FOUND: 5;
  ALREADY_EXISTS: 6;
  PERMISSION_DENIED: 7;
  UNAUTHENTICATED: 16;
  RESOURCE_EXHAUSTED: 8;
  FAILED_PRECONDITION: 9;
  ABORTED: 10;
  OUT_OF_RANGE: 11;
  UNIMPLEMENTED: 12;
  INTERNAL: 13;
  UNAVAILABLE: 14;
  DATA_LOSS: 15;
}

Usage Examples:

// Enhanced error handling in RPC implementation
function robustRpcImpl(method, requestData, callback) {
    try {
        // Validate method
        if (!method) {
            const error = new Error("Method not specified");
            error.code = RPCErrorCodes.INVALID_ARGUMENT;
            return callback(error);
        }
        
        // Make RPC call with timeout
        const timeoutId = setTimeout(() => {
            const error = new Error("RPC call timed out");
            error.code = RPCErrorCodes.DEADLINE_EXCEEDED;
            error.method = method.name;
            callback(error);
        }, 30000);
        
        makeRpcCall(method, requestData, (err, response) => {
            clearTimeout(timeoutId);
            
            if (err) {
                // Enhance error with context
                err.method = method.name;
                err.code = err.code || RPCErrorCodes.UNKNOWN;
            }
            
            callback(err, response);
        });
        
    } catch (err) {
        err.code = RPCErrorCodes.INTERNAL;
        err.method = method.name;
        callback(err);
    }
}

// Error handling in service calls
userService.getUser(request, function(err, response) {
    if (err) {
        switch (err.code) {
            case RPCErrorCodes.NOT_FOUND:
                console.log("User not found");
                break;
            case RPCErrorCodes.PERMISSION_DENIED:
                console.log("Access denied");
                break;
            case RPCErrorCodes.UNAVAILABLE:
                console.log("Service unavailable, retrying...");
                // Implement retry logic
                break;
            default:
                console.error("RPC error:", err.message);
        }
        return;
    }
    
    // Success
    console.log("User retrieved:", response);
});

Types

interface RPCImpl {
  (method: Method, requestData: Uint8Array, callback: RPCImplCallback): void;
}

interface RPCImplCallback {
  (error: Error | null, response?: Uint8Array): void;
}

interface ServiceMethodCallback<TResponse> {
  (error: Error | null, response?: TResponse): void;
}

interface Constructor<T> extends Function {
  new (...args: any[]): T;
}

Install with Tessl CLI

npx tessl i tessl/npm-protobufjs

docs

binary-io.md

cli-tools.md

code-generation.md

index.md

proto-loading.md

reflection.md

rpc-services.md

serialization.md

tile.json