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