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

metadata.mddocs/

Metadata System

Header and trailer management system for passing cross-cutting concerns and request context between client and server, supporting both binary and string values with flexible manipulation methods.

Capabilities

Metadata Class

Core class for managing gRPC metadata (headers and trailers) with support for multiple values per key and binary data.

/**
 * Class for handling gRPC metadata (headers and trailers)
 */
class Metadata {
  /**
   * Create a new Metadata instance
   * @param options - Optional metadata behavior configuration
   */
  constructor(options?: MetadataOptions);
  
  /**
   * Set a metadata entry, replacing any existing values
   * @param key - Metadata key (case-insensitive)
   * @param value - String or binary value
   */
  set(key: string, value: MetadataValue): void;
  
  /**
   * Add a metadata entry, preserving existing values
   * @param key - Metadata key (case-insensitive)
   * @param value - String or binary value
   */
  add(key: string, value: MetadataValue): void;
  
  /**
   * Remove all values for a metadata key
   * @param key - Metadata key to remove
   */
  remove(key: string): void;
  
  /**
   * Get all values for a metadata key
   * @param key - Metadata key to retrieve
   * @returns Array of values (empty if key doesn't exist)
   */
  get(key: string): MetadataValue[];
  
  /**
   * Get metadata as a plain object map
   * @returns Object with string keys and MetadataValue values
   */
  getMap(): { [key: string]: MetadataValue };
  
  /**
   * Create a deep copy of this metadata
   * @returns New Metadata instance with same contents
   */
  clone(): Metadata;
  
  /**
   * Merge another metadata instance into this one
   * @param other - Metadata to merge in (values are added, not replaced)
   */
  merge(other: Metadata): void;
}

Metadata Options

Configuration options affecting metadata behavior and call characteristics.

interface MetadataOptions {
  /** Mark this request as idempotent (safe to retry) */
  idempotentRequest?: boolean;
  /** Wait for connection to be ready before sending */
  waitForReady?: boolean;
  /** Mark this request as cacheable */
  cacheableRequest?: boolean;
  /** Cork the metadata (buffer before sending) */
  corked?: boolean;
}

Metadata Values

Type definition for metadata values supporting both string and binary data.

/**
 * Union type for metadata values
 * - string: For text-based headers
 * - Buffer: For binary headers (key must end with '-bin')
 */
type MetadataValue = string | Buffer;

Usage Examples

Basic Metadata Operations

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

// Create new metadata
const metadata = new Metadata();

// Add string headers
metadata.set('authorization', 'Bearer token123');
metadata.set('x-request-id', 'req-456');
metadata.set('x-user-agent', 'myapp/1.0');

// Add multiple values for same key
metadata.add('x-trace-id', 'trace-1');
metadata.add('x-trace-id', 'trace-2');

// Add binary data (key must end with '-bin')
const binaryData = Buffer.from('binary data', 'utf8');
metadata.set('custom-data-bin', binaryData);

// Retrieve values
const authHeaders = metadata.get('authorization'); // ['Bearer token123']
const traceIds = metadata.get('x-trace-id'); // ['trace-1', 'trace-2']

// Get as map
const map = metadata.getMap();
console.log(map);
// {
//   'authorization': 'Bearer token123',
//   'x-request-id': 'req-456',
//   'x-user-agent': 'myapp/1.0',
//   'x-trace-id': 'trace-2', // Last value for duplicate keys
//   'custom-data-bin': Buffer(...)
// }

Client-Side Metadata Usage

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

// Create client metadata
const metadata = new Metadata({
  waitForReady: true,
  idempotentRequest: true
});

metadata.set('authorization', 'Bearer ' + getAuthToken());
metadata.set('x-client-version', '1.2.3');
metadata.set('x-request-id', generateRequestId());

// Use with unary call
client.sayHello(
  { name: "World" },
  metadata, // Send metadata as headers
  (error, response) => {
    if (error) {
      console.error("Error metadata:", error.metadata?.getMap());
      return;
    }
    console.log("Response:", response);
  }
);

// Use with streaming call
const call = client.streamingCall(metadata);
call.write({ data: "message1" });
call.end();

Server-Side Metadata Handling

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

const server = new Server();

server.addService(serviceDefinition, {
  sayHello: (call, callback) => {
    // Read incoming metadata
    const incomingMetadata = call.metadata;
    const requestId = incomingMetadata.get('x-request-id')[0];
    const authHeader = incomingMetadata.get('authorization')[0];
    
    console.log("Request ID:", requestId);
    console.log("Auth:", authHeader);
    
    // Send initial metadata (headers)
    const responseMetadata = new Metadata();
    responseMetadata.set('x-server-version', '2.1.0');
    responseMetadata.set('x-processed-at', new Date().toISOString());
    call.sendMetadata(responseMetadata);
    
    // Process request
    const response = { message: `Hello ${call.request.name}` };
    
    // Send trailing metadata with response
    const trailingMetadata = new Metadata();
    trailingMetadata.set('x-execution-time-ms', '42');
    trailingMetadata.set('x-request-id', requestId as string);
    
    callback(null, response, trailingMetadata);
  },
  
  streamingMethod: (call) => {
    // Access metadata from streaming calls
    const metadata = call.metadata;
    const clientId = metadata.get('x-client-id')[0];
    
    // Send initial metadata
    const responseMetadata = new Metadata();
    responseMetadata.set('x-stream-id', generateStreamId());
    call.sendMetadata(responseMetadata);
    
    // Handle streaming data...
    call.on('data', (request) => {
      console.log(`Data from client ${clientId}:`, request);
    });
  }
});

Metadata Manipulation and Merging

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

// Create base metadata
const baseMetadata = new Metadata();
baseMetadata.set('x-service', 'user-service');
baseMetadata.set('x-version', '1.0');

// Create additional metadata
const authMetadata = new Metadata();
authMetadata.set('authorization', 'Bearer token');
authMetadata.set('x-user-id', '12345');

// Clone and merge
const requestMetadata = baseMetadata.clone();
requestMetadata.merge(authMetadata);

// Add request-specific data
requestMetadata.set('x-request-id', generateRequestId());
requestMetadata.add('x-trace-parent', getTraceParent());

console.log("Final metadata:", requestMetadata.getMap());

Binary Metadata

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

const metadata = new Metadata();

// Binary keys must end with '-bin'
const protobufData = Buffer.from([0x08, 0x96, 0x01]); // Some protobuf data
metadata.set('custom-proto-bin', protobufData);

// JSON data as binary
const jsonData = Buffer.from(JSON.stringify({ userId: 123, sessionId: "abc" }));
metadata.set('session-data-bin', jsonData);

// Retrieve binary data
const protoBuffer = metadata.get('custom-proto-bin')[0] as Buffer;
const sessionBuffer = metadata.get('session-data-bin')[0] as Buffer;
const sessionData = JSON.parse(sessionBuffer.toString('utf8'));

console.log("Session data:", sessionData);

Metadata with Call Options

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

// Create metadata with behavioral options
const metadata = new Metadata({
  waitForReady: true,        // Wait for connection
  idempotentRequest: true,   // Safe to retry
  cacheableRequest: false,   // Don't cache response
  corked: false             // Send immediately
});

metadata.set('authorization', 'Bearer token');

// Use with call options
const callOptions: CallOptions = {
  deadline: Date.now() + 30000, // 30 second timeout
};

client.getData(
  { query: "search term" },
  metadata,
  callOptions,
  (error, response) => {
    console.log("Response:", response);
  }
);

Error Metadata

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

// Server sending error with metadata
server.addService(serviceDefinition, {
  validateData: (call, callback) => {
    const errorMetadata = new Metadata();
    errorMetadata.set('x-error-code', 'VALIDATION_FAILED');
    errorMetadata.set('x-retry-after', '300'); // Retry after 5 minutes
    
    callback({
      code: status.INVALID_ARGUMENT,
      details: 'Input validation failed',
      metadata: errorMetadata
    });
  }
});

// Client handling error metadata
client.validateData({ data: "invalid" }, (error, response) => {
  if (error) {
    const errorCode = error.metadata?.get('x-error-code')[0];
    const retryAfter = error.metadata?.get('x-retry-after')[0];
    
    console.error(`Error ${errorCode}, retry after ${retryAfter}s`);
    return;
  }
  console.log("Success:", response);
});

Metadata Interceptor Pattern

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

// Client interceptor that adds authentication metadata
const authInterceptor: Interceptor = (options, nextCall) => {
  return new InterceptingCall(nextCall(options), {
    start: function(metadata, listener, next) {
      // Add auth to all outgoing requests
      metadata.set('authorization', `Bearer ${getCurrentToken()}`);
      metadata.set('x-request-timestamp', Date.now().toString());
      next(metadata, listener);
    }
  });
};

// Server interceptor that logs metadata
const loggingInterceptor: ServerInterceptor = (methodDefinition, call) => {
  const metadata = call.metadata;
  const requestId = metadata.get('x-request-id')[0] || 'unknown';
  
  console.log(`[${requestId}] ${methodDefinition.path} started`);
  console.log(`[${requestId}] Metadata:`, metadata.getMap());
  
  return call; // Continue with original call
};