{"library":"nice-grpc-server-middleware-terminator","title":"nice-grpc Server Middleware Terminator","description":"The `nice-grpc-server-middleware-terminator` package provides a crucial server middleware for `nice-grpc` to facilitate graceful shutdown of gRPC services by actively terminating long-running calls. It addresses a common challenge in gRPC, particularly with streaming RPCs, where `server.shutdown()` would otherwise block indefinitely until all in-flight requests complete or are cancelled by the client. This middleware injects a `context.signal` into service implementations; when `server.shutdown()` is initiated, this signal is aborted, prompting the service method to stop processing. Clients of terminated calls receive a `UNAVAILABLE: Server shutting down` gRPC error, ensuring that the server can complete its shutdown process within a predictable timeframe. The current stable version is 2.0.17, with updates typically coinciding with the broader `nice-grpc` ecosystem. Its key differentiator is providing a standardized, integrated mechanism within the `nice-grpc` framework for managing server-side RPC lifecycle during shutdown, directly supporting the modern `AbortSignal` pattern.","language":"javascript","status":"active","last_verified":"Sun Apr 19","install":{"commands":["npm install nice-grpc-server-middleware-terminator"],"cli":null},"imports":["import { createTerminatorMiddleware } from 'nice-grpc-server-middleware-terminator';","import type { TerminatorContext } from 'nice-grpc-server-middleware-terminator';","import { isTerminated } from 'nice-grpc-server-middleware-terminator';"],"auth":{"required":false,"env_vars":[]},"quickstart":{"code":"import {createServer, ServiceImplementation, ServerError, Status, CallContext} from 'nice-grpc';\nimport { createTerminatorMiddleware, TerminatorContext } from 'nice-grpc-server-middleware-terminator';\nimport { AbortController } from 'abort-controller-x';\nimport * as path from 'path';\nimport { loadSync } from '@grpc/proto-loader';\nimport { GrpcObject, PackageDefinition, UntypedServiceImplementation } from '@grpc/grpc-js';\n\ninterface ExampleRequest { name: string; }\ninterface ExampleResponse { message: string; }\n\n// 1. Define your Protobuf service (example.proto)\n// syntax = \"proto3\";\n// package example;\n// service ExampleService {\n//   rpc SayHello (ExampleRequest) returns (ExampleResponse) {}\n//   rpc StreamHello (ExampleRequest) returns (stream ExampleResponse) {}\n// }\n\nconst PROTO_PATH = path.resolve(__dirname, 'example.proto');\nconst packageDefinition: PackageDefinition = loadSync(PROTO_PATH);\nconst grpcObject: GrpcObject = {} as GrpcObject; // Simplified for example, normally loads via @grpc/proto-loader\n\n// Simulate the generated service definition\nconst ExampleServiceDefinition = {\n  name: 'example.ExampleService',\n  fullName: 'example.ExampleService',\n  methods: {\n    SayHello: {\n      path: '/example.ExampleService/SayHello',\n      requestStream: false,\n      responseStream: false,\n      requestType: {}, // Simplified\n      responseType: {}, // Simplified\n      responseDeserialize: (b: Buffer) => ({ message: b.toString() }),\n      requestSerialize: (m: ExampleRequest) => Buffer.from(m.name),\n    },\n    StreamHello: {\n      path: '/example.ExampleService/StreamHello',\n      requestStream: false,\n      responseStream: true,\n      requestType: {}, // Simplified\n      responseType: {}, // Simplified\n      responseDeserialize: (b: Buffer) => ({ message: b.toString() }),\n      requestSerialize: (m: ExampleRequest) => Buffer.from(m.name),\n    }\n  }\n};\n\ntype MyCallContext = CallContext & TerminatorContext;\n\nconst exampleServiceImpl: ServiceImplementation<typeof ExampleServiceDefinition, MyCallContext> = {\n  async SayHello(request: ExampleRequest, context: MyCallContext): Promise<ExampleResponse> {\n    console.log(`[Server] Received SayHello from ${request.name}`);\n    return { message: `Hello, ${request.name}!` };\n  },\n  async *StreamHello(request: ExampleRequest, context: MyCallContext): AsyncIterable<ExampleResponse> {\n    console.log(`[Server] Received StreamHello from ${request.name}. Starting stream...`);\n    let counter = 0;\n    while (true) {\n      try {\n        // IMPORTANT: Check context.signal.aborted regularly in long-running operations\n        // to respond to termination requests.\n        if (context.signal.aborted) {\n          console.log(`[Server] StreamHello for ${request.name} aborted due to server shutdown.`);\n          // This is generally handled by the middleware, but explicit checks are good.\n          throw new ServerError(Status.UNAVAILABLE, 'Server shutting down');\n        }\n        yield { message: `Stream Hello ${counter++} to ${request.name}` };\n        await new Promise(resolve => setTimeout(resolve, 500)); // Simulate work\n      } catch (error) {\n        if (error instanceof ServerError && error.code === Status.UNAVAILABLE) {\n           console.log(`[Server] Explicitly caught ServerError for ${request.name}.`);\n           break; // Exit the stream gracefully\n        }\n        console.error(`[Server] Stream error for ${request.name}:`, error);\n        throw error;\n      }\n    }\n  },\n};\n\nasync function runServer() {\n  const server = createServer()\n    .use(createTerminatorMiddleware())\n    .add(ExampleServiceDefinition, exampleServiceImpl as UntypedServiceImplementation);\n\n  const port = 50051;\n  await server.listen(`0.0.0.0:${port}`);\n  console.log(`gRPC server listening on port ${port}`);\n\n  // Simulate a client making a long-running streaming call\n  // (In a real scenario, this would be a separate client process)\n  async function simulateClient() {\n    const client = createServer().createClient(ExampleServiceDefinition, `0.0.0.0:${port}`);\n    console.log('[Client] Initiating SayHello...');\n    const unaryResponse = await client.SayHello({ name: 'Alice' });\n    console.log('[Client] SayHello response:', unaryResponse.message);\n\n    console.log('[Client] Initiating StreamHello...');\n    try {\n      for await (const response of client.StreamHello({ name: 'Bob' })) {\n        console.log('[Client] StreamHello response:', response.message);\n        // Simulate client processing for a bit before server shutdown\n        await new Promise(resolve => setTimeout(resolve, 300));\n      }\n    } catch (error) {\n      if (error instanceof ServerError && error.code === Status.UNAVAILABLE) {\n        console.warn('[Client] Stream terminated by server during shutdown as expected.');\n      } else {\n        console.error('[Client] StreamHello error:', error);\n      }\n    }\n  }\n\n  // Start the simulated client call after a short delay\n  setTimeout(simulateClient, 1000);\n\n  // Simulate graceful shutdown after some time\n  setTimeout(async () => {\n    console.log('\\n[Server] Initiating graceful shutdown...');\n    await server.shutdown();\n    console.log('[Server] Server shut down gracefully.');\n  }, 5000);\n}\n\nrunServer().catch(console.error);\n","lang":"typescript","description":"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.","tag":null,"tag_description":null,"last_tested":null,"results":[]},"compatibility":null}