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

service-definitions.mddocs/

Service Definitions

Tools for loading Protocol Buffer definitions and generating client constructors for type-safe service communication, enabling dynamic service discovery and client generation from proto files.

Capabilities

Package Definition Loading

Load Protocol Buffer package definitions into gRPC service objects for client and server use.

/**
 * Load a package definition from proto-loader into gRPC objects
 * @param packageDef - Package definition from @grpc/proto-loader
 * @returns Hierarchical object containing service definitions
 */
function loadPackageDefinition(packageDef: PackageDefinition): GrpcObject;

Client Constructor Generation

Create client constructor functions from service definitions for type-safe service communication.

/**
 * Create a client constructor from a service definition
 * @param methods - Service definition containing method definitions
 * @param serviceName - Name of the service for error messages
 * @param classOptions - Additional options for the generated class
 * @returns Constructor function for creating service clients
 */
function makeClientConstructor(
  methods: ServiceDefinition,
  serviceName: string,
  classOptions?: {}
): ServiceClientConstructor;

/**
 * Alias for makeClientConstructor (deprecated name)
 */
const makeGenericClientConstructor: typeof makeClientConstructor;

Service Definition Types

Core interfaces for defining gRPC services and methods.

/**
 * Definition of a complete gRPC service
 */
interface ServiceDefinition {
  [methodName: string]: MethodDefinition<any, any>;
}

/**
 * Definition of a single gRPC method
 */
interface MethodDefinition<RequestType, ResponseType> {
  /** Full path of the method (e.g., /package.Service/Method) */
  path: string;
  /** Whether the client streams multiple requests */
  requestStream: boolean;
  /** Whether the server streams multiple responses */
  responseStream: boolean;
  /** Function to serialize request messages */
  requestSerialize: Serialize<RequestType>;
  /** Function to deserialize response messages */
  responseDeserialize: Deserialize<ResponseType>;
  /** Original method name from proto file */
  originalName?: string;
}

/**
 * Client-specific method definition
 */
interface ClientMethodDefinition<RequestType, ResponseType> extends MethodDefinition<RequestType, ResponseType> {
  /** Response serializer (for server streaming) */
  responseSerialize: Serialize<ResponseType>;
  /** Request deserializer (for client streaming) */
  requestDeserialize: Deserialize<RequestType>;
}

/**
 * Server-specific method definition
 */
interface ServerMethodDefinition<RequestType, ResponseType> extends MethodDefinition<RequestType, ResponseType> {
  /** Request deserializer */
  requestDeserialize: Deserialize<RequestType>;
  /** Response serializer */
  responseSerialize: Serialize<ResponseType>;
}

Serialization Functions

Type definitions for message serialization and deserialization.

/**
 * Function to serialize messages to Buffer
 */
interface Serialize<T> {
  (value: T): Buffer;
}

/**
 * Function to deserialize messages from Buffer
 */
interface Deserialize<T> {
  (bytes: Buffer): T;
}

Generated Client Types

Interfaces for generated client constructors and instances.

/**
 * Constructor function for service clients
 */
interface ServiceClientConstructor {
  new (address: string, credentials: ChannelCredentials, options?: Partial<ClientOptions>): ServiceClient;
  service: ServiceDefinition;
}

/**
 * Generated service client interface
 */
interface ServiceClient extends Client {
  [methodName: string]: any; // Dynamic methods based on service definition
}

/**
 * Hierarchical object containing services and nested packages
 */
interface GrpcObject {
  [name: string]: GrpcObject | ServiceClientConstructor | ProtobufTypeDefinition;
}

/**
 * Package definition input from proto-loader
 */
interface PackageDefinition {
  [fullyQualifiedName: string]: ServiceDefinition | ProtobufTypeDefinition;
}

/**
 * Protobuf type definition for messages and enums
 */
interface ProtobufTypeDefinition {
  format: string;
  type: object;
  fileDescriptorProtos: Buffer[];
}

Usage Examples

Loading Services from Proto Files

import { loadSync } from "@grpc/proto-loader";
import { loadPackageDefinition, credentials } from "@grpc/grpc-js";

// Load proto file using proto-loader
const packageDefinition = loadSync("path/to/service.proto", {
  keepCase: true,
  longs: String,
  enums: String,
  defaults: true,
  oneofs: true
});

// Load into gRPC
const protoDescriptor = loadPackageDefinition(packageDefinition);

// Access service constructor
const MyServiceClient = protoDescriptor.mypackage.MyService;

// Create client instance
const client = new MyServiceClient(
  "localhost:50051",
  credentials.createInsecure()
);

Creating Clients with makeClientConstructor

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

// Define service manually (usually comes from proto loading)
const serviceDefinition: ServiceDefinition = {
  sayHello: {
    path: '/greeter.Greeter/SayHello',
    requestStream: false,
    responseStream: false,
    requestSerialize: (value: any) => Buffer.from(JSON.stringify(value)),
    responseDeserialize: (bytes: Buffer) => JSON.parse(bytes.toString())
  },
  
  streamGreetings: {
    path: '/greeter.Greeter/StreamGreetings',
    requestStream: false,
    responseStream: true,
    requestSerialize: (value: any) => Buffer.from(JSON.stringify(value)),
    responseDeserialize: (bytes: Buffer) => JSON.parse(bytes.toString())
  }
};

// Create client constructor
const GreeterClient = makeClientConstructor(
  serviceDefinition,
  'GreeterService'
);

// Use the generated client
const client = new GreeterClient(
  "localhost:50051",
  credentials.createInsecure()
);

// Make unary call
client.sayHello({ name: "World" }, (error, response) => {
  if (error) {
    console.error("Error:", error);
    return;
  }
  console.log("Response:", response);
});

// Make streaming call
const stream = client.streamGreetings({ count: 5 });
stream.on('data', (response) => {
  console.log("Greeting:", response);
});

Complete Proto Integration Example

import { loadSync } from "@grpc/proto-loader";
import { 
  loadPackageDefinition, 
  credentials, 
  Server, 
  ServerCredentials 
} from "@grpc/grpc-js";

// Load proto file
const PROTO_PATH = "./protos/greeter.proto";
const packageDefinition = loadSync(PROTO_PATH, {
  keepCase: true,
  longs: String,
  enums: String,
  defaults: true,
  oneofs: true,
  includeDirs: ["./protos"] // Additional proto directories
});

const grpcObject = loadPackageDefinition(packageDefinition);

// Access nested service (e.g., com.example.greeter.GreeterService)
const GreeterService = grpcObject.com?.example?.greeter?.GreeterService;

if (!GreeterService) {
  throw new Error("Service not found in proto definition");
}

// Create server and add service
const server = new Server();
server.addService(GreeterService.service, {
  sayHello: (call, callback) => {
    callback(null, { 
      message: `Hello ${call.request.name}!` 
    });
  },
  
  sayHelloStream: (call) => {
    const name = call.request.name;
    for (let i = 0; i < 5; i++) {
      call.write({ 
        message: `Hello ${name} #${i + 1}` 
      });
    }
    call.end();
  }
});

// Start server
server.bindAsync(
  "0.0.0.0:50051",
  ServerCredentials.createInsecure(),
  (error, port) => {
    if (error) {
      console.error("Server bind failed:", error);
      return;
    }
    console.log(`Server running on port ${port}`);
    server.start();
  }
);

// Create client
const client = new GreeterService(
  "localhost:50051",
  credentials.createInsecure()
);

// Use client
client.sayHello({ name: "gRPC" }, (error, response) => {
  console.log("Server response:", response?.message);
});

Advanced Service Definition

import { makeClientConstructor, ServiceDefinition } from "@grpc/grpc-js";

// Complex service with all call types
const advancedServiceDefinition: ServiceDefinition = {
  // Unary call
  getUser: {
    path: '/users.UserService/GetUser',
    requestStream: false,
    responseStream: false,
    requestSerialize: serializeUserRequest,
    responseDeserialize: deserializeUser,
    originalName: 'GetUser'
  },
  
  // Client streaming
  uploadFiles: {
    path: '/files.FileService/UploadFiles',
    requestStream: true,
    responseStream: false,
    requestSerialize: serializeFileChunk,
    responseDeserialize: deserializeUploadResponse
  },
  
  // Server streaming
  watchEvents: {
    path: '/events.EventService/WatchEvents',
    requestStream: false,
    responseStream: true,
    requestSerialize: serializeEventQuery,
    responseDeserialize: deserializeEvent
  },
  
  // Bidirectional streaming
  chat: {
    path: '/chat.ChatService/Chat',
    requestStream: true,
    responseStream: true,
    requestSerialize: serializeChatMessage,
    responseDeserialize: deserializeChatMessage
  }
};

// Create typed client
const ServiceClient = makeClientConstructor(
  advancedServiceDefinition,
  'AdvancedService'
);

// Custom serialization functions
function serializeUserRequest(request: { id: string }): Buffer {
  return Buffer.from(JSON.stringify(request));
}

function deserializeUser(bytes: Buffer): { id: string; name: string; email: string } {
  return JSON.parse(bytes.toString());
}

function serializeFileChunk(chunk: { filename: string; data: Buffer }): Buffer {
  // Custom serialization logic
  return Buffer.concat([
    Buffer.from(chunk.filename.length.toString().padStart(4, '0')),
    Buffer.from(chunk.filename),
    chunk.data
  ]);
}

Proto Loader Integration Patterns

import { loadSync, loadFileDescriptorSetFromBuffer } from "@grpc/proto-loader";
import { loadPackageDefinition } from "@grpc/grpc-js";

// Load with custom options
const packageDefinition = loadSync("service.proto", {
  keepCase: true,           // Keep original field names
  longs: String,           // Convert longs to strings
  enums: String,           // Convert enums to strings  
  bytes: Array,            // Convert bytes to arrays
  defaults: true,          // Include default values
  arrays: true,            // Use arrays for repeated fields
  objects: true,           // Use objects for maps
  oneofs: true,            // Include oneof fields
  includeDirs: [           // Additional proto directories
    "./protos",
    "./vendor/protos"
  ]
});

// Load from descriptor set (compiled protos)
const descriptorSetBuffer = readFileSync("compiled.pb");
const packageDefinitionFromDescriptor = loadFileDescriptorSetFromBuffer(
  descriptorSetBuffer
);

// Load both into gRPC
const services = loadPackageDefinition(packageDefinition);
const compiledServices = loadPackageDefinition(packageDefinitionFromDescriptor);

// Handle deeply nested packages
const deepService = services.com?.company?.division?.team?.ServiceName;
if (deepService && typeof deepService === 'function') {
  const client = new deepService(address, credentials.createInsecure());
}

Type Safety with Service Definitions

// Type-safe service definition creation
interface HelloRequest {
  name: string;
}

interface HelloResponse {
  message: string;
}

const typedServiceDefinition: ServiceDefinition = {
  sayHello: {
    path: '/greeter.Greeter/SayHello',
    requestStream: false,
    responseStream: false,
    requestSerialize: (req: HelloRequest) => 
      Buffer.from(JSON.stringify(req)),
    responseDeserialize: (bytes: Buffer): HelloResponse => 
      JSON.parse(bytes.toString()),
    originalName: 'SayHello'
  }
};

// Generated client will be type-safe
const TypedClient = makeClientConstructor(
  typedServiceDefinition,
  'TypedGreeter'
);

const client = new TypedClient(address, credentials.createInsecure());

// TypeScript will infer correct types
client.sayHello({ name: "TypeScript" }, (error, response) => {
  if (response) {
    console.log(response.message); // TypeScript knows this is a string
  }
});