Interceptor
Interceptors wrap the RPC method. Code before await next() runs after guards and before the method. Code after await next() runs after the method returns and before post-handler middleware.
interface Interceptor<TRequest = any, TResponse = any> {
intercept(
request: TRequest,
response: TResponse,
next: () => Promise<InterceptorTransformObject>,
): Promise<void>;
}
interface InterceptorTransformObject {
push: (transform: (value: unknown) => unknown) => void;
}
request is the @grpc/grpc-js call. response is the callback for unary and client-streaming RPCs, and the call for streaming responses.
transform.push() changes the value the method returns before the adapter sends it. That applies to unary and client-streaming methods that return a message and do not use @Callback(). Streamed messages and callback replies stay as the method wrote them. Skip next() to skip the method. Throw a GrpcError to finish the call with that status.
Add @injectable() and bind the interceptor.
import {
type Interceptor,
type InterceptorTransformObject,
RPC,
Service,
UseInterceptor,
} from '@inversifyjs/grpc-core';
import { injectable } from 'inversify';
import {
type HeroRequest,
type HeroResponse,
heroServiceDefinition,
} from './loadHeroServiceDefinition.js';
function isHeroResponse(value: unknown): value is HeroResponse {
return (
typeof value === 'object' &&
value !== null &&
'name' in value &&
typeof value.name === 'string'
);
}
@injectable()
export class SuffixInterceptor implements Interceptor {
public async intercept(
_call: unknown,
_response: unknown,
next: () => Promise<InterceptorTransformObject>,
): Promise<void> {
const transform: InterceptorTransformObject = await next();
transform.push((value: unknown): unknown => {
if (!isHeroResponse(value)) {
return value;
}
return {
name: `${value.name}!`,
};
});
}
}
@Service(heroServiceDefinition)
@UseInterceptor(SuffixInterceptor)
export class InterceptedHeroService {
@RPC('GetHero')
public getHero(call: { request: HeroRequest }): HeroResponse {
return {
name: call.request.id,
};
}
}
GetHero('hero-1') returns { name: 'hero-1!' }.
@UseInterceptor() on the class wraps every RPC. On a method, it wraps that RPC. Class interceptors are outside method interceptors. Global interceptors, registered with useGlobalInterceptors() before build(), sit inside both.