Nice-GRPC Client Deadline Middleware
The `nice-grpc-client-middleware-deadline` package provides client-side middleware for the `nice-grpc` library, enabling developers to easily set and enforce deadlines for outgoing gRPC calls. When a configured deadline is exceeded, the in-flight gRPC call is automatically cancelled, and the client receives a `ClientError` with the `DEADLINE_EXCEEDED` status code. This functionality is crucial for building resilient microservices by preventing requests from running indefinitely, which could otherwise lead to resource exhaustion and cascading failures. The package is currently at version 2.0.18, with a sustainable maintenance record and consistent updates aligning with the `nice-grpc` ecosystem's focus on modern TypeScript, Promises, and Async Iterables. It is a key component in `nice-grpc`'s modular approach to client-side concerns, offering a clear differentiator in its modern API design compared to more traditional gRPC implementations.
Common errors
-
Error [ERR_REQUIRE_ESM]: require() of ES Module /path/to/node_modules/nice-grpc-client-middleware-deadline/index.js not supported.
cause Attempting to import an ESM-only `nice-grpc` package using CommonJS `require()` syntax in a CommonJS project.fixUpdate your project to use ES module `import` statements and ensure your `package.json` includes `"type": "module"`, or rename your file to `.mjs`. Alternatively, use dynamic `import()` within your CJS file: `const { createDeadlineClientMiddleware } = await import('nice-grpc-client-middleware-deadline');` -
ClientError: 4 DEADLINE_EXCEEDED
cause The gRPC call did not complete within the specified deadline, or the deadline was set too short.fixReview the configured `deadline` value. Ensure it provides sufficient time for the server to process the request and send a response. Consider network latency and server processing time when setting deadlines. For debugging, temporarily increase the deadline to confirm if it's a timeout issue versus a different server-side error. -
Property 'deadline' does not exist on type 'CallOptions'.
cause The TypeScript compiler reports this if `deadline` is added to `CallOptions` without the `createDeadlineClientMiddleware` correctly augmenting the `CallOptions` type, or if a `nice-grpc` client is created without the middleware being applied.fixEnsure `createDeadlineClientMiddleware` is applied to your `createClientFactory` instance *before* creating the client. The middleware is responsible for extending the `CallOptions` type to include the `deadline` property.
Warnings
- breaking The `nice-grpc` ecosystem, including `nice-grpc-client-middleware-deadline`, adopted an ESM-first approach starting with `nice-grpc` v2. Projects using CommonJS (`require()`) syntax for importing these packages will encounter `ERR_REQUIRE_ESM` errors.
- gotcha gRPC deadlines represent an *absolute point in time* (e.g., a Unix timestamp in milliseconds), not a duration. Setting a deadline to `Date.now() + durationMs` is common, but providing a past or current `Date.now()` value will cause the RPC to fail immediately with `DEADLINE_EXCEEDED`.
- gotcha While the client-side deadline middleware successfully cancels the client's expectation of a response, the gRPC call on the server side may continue to execute unless the server's application logic explicitly checks and respects the `ServerCallContext.CancellationToken` that gRPC provides.
- gotcha Applying universal or long-duration deadlines via middleware to server-streaming RPCs can prematurely terminate streams if the deadline expires before the server has sent all intended messages. This might result in incomplete data on the client side without a clear indication of a server-side error.
Install
-
npm install nice-grpc-client-middleware-deadline -
yarn add nice-grpc-client-middleware-deadline -
pnpm add nice-grpc-client-middleware-deadline
Imports
- createDeadlineClientMiddleware
const { createDeadlineClientMiddleware } = require('nice-grpc-client-middleware-deadline');import { createDeadlineClientMiddleware } from 'nice-grpc-client-middleware-deadline'; - createClientFactory
import { createClientFactory } from 'nice-grpc/client';import { createClientFactory } from 'nice-grpc'; - ClientError
import { ClientError } from 'nice-grpc';import { ClientError } from 'nice-grpc-common';
Quickstart
import { createChannel, createClientFactory, createClient, ClientError, status } from 'nice-grpc';
import { createDeadlineClientMiddleware } from 'nice-grpc-client-middleware-deadline';
import { ProtoGrpcType } from './greeter'; // Assuming you have compiled protobufs
import { GreeterClient, GreeterDefinition } from './greeter/Greeter'; // Adjust based on your proto compilation
interface HelloRequest {
name: string;
}
interface HelloResponse {
message: string;
}
// Mock Service Definition (replace with your compiled proto definition)
const GreeterServiceDefinition: GreeterDefinition = {
// @ts-ignore - In a real scenario, this would come from compiled protos
name: 'Greeter',
// @ts-ignore
methods: {
SayHello: {
path: '/Greeter/SayHello',
requestStream: false,
responseStream: false,
requestType: { type: 'HelloRequest' },
responseType: { type: 'HelloResponse' },
},
},
};
async function main() {
const channel = createChannel('localhost:50051');
const clientFactory = createClientFactory()
.use(createDeadlineClientMiddleware());
const client: GreeterClient = clientFactory.create(GreeterServiceDefinition, channel);
console.log('Client created with deadline middleware.');
try {
// Example 1: Call with a specific deadline (500 ms from now)
const response1 = await client.sayHello({
name: 'Alice',
}, {
deadline: Date.now() + 500, // Call will timeout if server doesn't respond in 500ms
});
console.log(`Response from SayHello (Alice): ${response1.message}`);
} catch (error) {
if (error instanceof ClientError && error.code === status.DEADLINE_EXCEEDED) {
console.error('Call for Alice exceeded deadline!');
} else {
console.error('Error calling SayHello for Alice:', error);
}
}
try {
// Example 2: Call without an explicit deadline (will use any factory-wide default or gRPC's default)
const response2 = await client.sayHello({
name: 'Bob',
});
console.log(`Response from SayHello (Bob): ${response2.message}`);
} catch (error) {
console.error('Error calling SayHello for Bob:', error);
}
// To demonstrate a factory-wide default deadline:
const clientFactoryWithDefaultDeadline = createClientFactory()
.use(createDeadlineClientMiddleware())
.use(async (call, options) => call.next(call, { ...options, deadline: Date.now() + 2000 })); // 2-second default
const clientWithDefault: GreeterClient = clientFactoryWithDefaultDeadline.create(GreeterServiceDefinition, channel);
try {
console.log('\nAttempting call with 2-second default deadline...');
const response3 = await clientWithDefault.sayHello({ name: 'Charlie' });
console.log(`Response from SayHello (Charlie): ${response3.message}`);
} catch (error) {
if (error instanceof ClientError && error.code === status.DEADLINE_EXCEEDED) {
console.error('Call for Charlie exceeded default deadline!');
} else {
console.error('Error calling SayHello for Charlie:', error);
}
}
channel.close();
}
main().catch(console.error);