Registry /
http-networking / nice-grpc-server-middleware-terminator
Install & Compatibility
Where this runs
tested against v? · npm install
Install × environment matrix
Each cell = how many times install + import succeeded across repeated harness runs. Partial = flaky.
glibc = Debian/Ubuntu slim · musl = Alpine Linux
muslnode 18–226 runs
build_error
glibcnode 18–226 runs
build_error
Code
Verified usage
Verified import paths — ran on the pinned version, not inferred.
createTerminatorMiddleware
✓ import { createTerminatorMiddleware } from 'nice-grpc-server-middleware-terminator';
✗ const createTerminatorMiddleware = require('nice-grpc-server-middleware-terminator');
The `nice-grpc` ecosystem, and by extension its middleware, is primarily designed for ES Modules. CommonJS `require()` is not supported.
TerminatorContext
✓ import type { TerminatorContext } from 'nice-grpc-server-middleware-terminator';
✗ import { TerminatorContext } from 'nice-grpc-server-middleware-terminator';
This is a type definition used to augment `nice-grpc`'s `CallContext`. Use `import type` to avoid bundling unnecessary runtime code.
isTerminated
✓ import { isTerminated } from 'nice-grpc-server-middleware-terminator';
A utility function to check if a `nice-grpc` `ServerError` indicates termination by this middleware.
Demonstrates setting up a `nice-grpc` server with the terminator middleware, initiating a long-running streaming call, and observing the graceful shutdown process which aborts the stream.
import {createServer, ServiceImplementation, ServerError, Status, CallContext} from 'nice-grpc';
import { createTerminatorMiddleware, TerminatorContext } from 'nice-grpc-server-middleware-terminator';
import { AbortController } from 'abort-controller-x';
import * as path from 'path';
import { loadSync } from '@grpc/proto-loader';
import { GrpcObject, PackageDefinition, UntypedServiceImplementation } from '@grpc/grpc-js';
interface ExampleRequest { name: string; }
interface ExampleResponse { message: string; }
// 1. Define your Protobuf service (example.proto)
// syntax = "proto3";
// package example;
// service ExampleService {
// rpc SayHello (ExampleRequest) returns (ExampleResponse) {}
// rpc StreamHello (ExampleRequest) returns (stream ExampleResponse) {}
// }
const PROTO_PATH = path.resolve(__dirname, 'example.proto');
const packageDefinition: PackageDefinition = loadSync(PROTO_PATH);
const grpcObject: GrpcObject = {} as GrpcObject; // Simplified for example, normally loads via @grpc/proto-loader
// Simulate the generated service definition
const ExampleServiceDefinition = {
name: 'example.ExampleService',
fullName: 'example.ExampleService',
methods: {
SayHello: {
path: '/example.ExampleService/SayHello',
requestStream: false,
responseStream: false,
requestType: {}, // Simplified
responseType: {}, // Simplified
responseDeserialize: (b: Buffer) => ({ message: b.toString() }),
requestSerialize: (m: ExampleRequest) => Buffer.from(m.name),
},
StreamHello: {
path: '/example.ExampleService/StreamHello',
requestStream: false,
responseStream: true,
requestType: {}, // Simplified
responseType: {}, // Simplified
responseDeserialize: (b: Buffer) => ({ message: b.toString() }),
requestSerialize: (m: ExampleRequest) => Buffer.from(m.name),
}
}
};
type MyCallContext = CallContext & TerminatorContext;
const exampleServiceImpl: ServiceImplementation<typeof ExampleServiceDefinition, MyCallContext> = {
async SayHello(request: ExampleRequest, context: MyCallContext): Promise<ExampleResponse> {
console.log(`[Server] Received SayHello from ${request.name}`);
return { message: `Hello, ${request.name}!` };
},
async *StreamHello(request: ExampleRequest, context: MyCallContext): AsyncIterable<ExampleResponse> {
console.log(`[Server] Received StreamHello from ${request.name}. Starting stream...`);
let counter = 0;
while (true) {
try {
// IMPORTANT: Check context.signal.aborted regularly in long-running operations
// to respond to termination requests.
if (context.signal.aborted) {
console.log(`[Server] StreamHello for ${request.name} aborted due to server shutdown.`);
// This is generally handled by the middleware, but explicit checks are good.
throw new ServerError(Status.UNAVAILABLE, 'Server shutting down');
}
yield { message: `Stream Hello ${counter++} to ${request.name}` };
await new Promise(resolve => setTimeout(resolve, 500)); // Simulate work
} catch (error) {
if (error instanceof ServerError && error.code === Status.UNAVAILABLE) {
console.log(`[Server] Explicitly caught ServerError for ${request.name}.`);
break; // Exit the stream gracefully
}
console.error(`[Server] Stream error for ${request.name}:`, error);
throw error;
}
}
},
};
async function runServer() {
const server = createServer()
.use(createTerminatorMiddleware())
.add(ExampleServiceDefinition, exampleServiceImpl as UntypedServiceImplementation);
const port = 50051;
await server.listen(`0.0.0.0:${port}`);
console.log(`gRPC server listening on port ${port}`);
// Simulate a client making a long-running streaming call
// (In a real scenario, this would be a separate client process)
async function simulateClient() {
const client = createServer().createClient(ExampleServiceDefinition, `0.0.0.0:${port}`);
console.log('[Client] Initiating SayHello...');
const unaryResponse = await client.SayHello({ name: 'Alice' });
console.log('[Client] SayHello response:', unaryResponse.message);
console.log('[Client] Initiating StreamHello...');
try {
for await (const response of client.StreamHello({ name: 'Bob' })) {
console.log('[Client] StreamHello response:', response.message);
// Simulate client processing for a bit before server shutdown
await new Promise(resolve => setTimeout(resolve, 300));
}
} catch (error) {
if (error instanceof ServerError && error.code === Status.UNAVAILABLE) {
console.warn('[Client] Stream terminated by server during shutdown as expected.');
} else {
console.error('[Client] StreamHello error:', error);
}
}
}
// Start the simulated client call after a short delay
setTimeout(simulateClient, 1000);
// Simulate graceful shutdown after some time
setTimeout(async () => {
console.log('\n[Server] Initiating graceful shutdown...');
await server.shutdown();
console.log('[Server] Server shut down gracefully.');
}, 5000);
}
runServer().catch(console.error);
Debug
Known issues
gotchaThe `nice-grpc-server-middleware-terminator` effectively terminates long-running calls only if the service implementation actively monitors `context.signal.aborted` (or similar AbortSignal patterns) and ceases work. If the service method does not check the signal, the middleware cannot force a stop, and the shutdown will still block.fixEnsure all long-running or streaming gRPC service methods frequently check `context.signal.aborted` and handle the termination by throwing a `ServerError` with `Status.UNAVAILABLE` or returning early.
affects: >=1.0.0
breakingWhile this middleware is at v2, its core dependency `nice-grpc` had a significant breaking change with its v3 release, becoming ESM-only. If you upgrade `nice-grpc` to v3, this middleware (and your application) must also be running in an ESM context.fixMigrate your project to use ES Modules (e.g., set `"type": "module"` in `package.json` and use `import`/`export` syntax). Ensure your build tools and Node.js environment support ESM.
affects: >=2.0.0 (in context of nice-grpc >=3.0.0)
gotchaWhen a call is terminated by this middleware during server shutdown, the client will receive a gRPC `UNAVAILABLE` status code with the message 'Server shutting down'. This is intended behavior, but might be misinterpreted as an unexpected error by clients if not handled explicitly.fixClients should implement robust error handling for `ServerError` where `error.code === Status.UNAVAILABLE` and `error.details` contains the specific shutdown message, allowing for graceful client-side disconnection or retry logic.
affects: >=1.0.0
Errors
Common errors & fixes
Server shutdown blocked indefinitely (or 'server.shutdown() never resolves')
Long-running gRPC calls (especially streaming ones) are not reacting to the termination signal provided by the middleware.
fixModify your service implementation methods to regularly check `context.signal.aborted` and gracefully exit or throw a `ServerError(Status.UNAVAILABLE, 'Server shutting down')` when the signal is aborted.
TypeError: createTerminatorMiddleware is not a function (or 'require is not defined')
Attempting to use ES Module syntax (`import`) in a CommonJS environment, or vice-versa, particularly if `nice-grpc` or its middleware is used in a mixed environment.
fixEnsure your project is consistently configured for either ES Modules (recommended for `nice-grpc` v3+) or CommonJS. For ESM, set `"type": "module"` in `package.json` and use `import` statements. For CommonJS, you might need to find a CommonJS compatible version or transpilation if available, but `nice-grpc` ecosystem strongly favors ESM.
Audit
Dependencies
nice-grpcrequiredCore gRPC server library that this package extends with termination middleware.
nice-grpc-commonrequiredProvides common types and utilities, including `CallContext` and `ServerError`, used by `nice-grpc` middleware.
abort-controller-xrequiredProvides cross-platform AbortController implementation used for signal propagation.