Fallback interceptor
A fallback supplies a deliberate substitute result after a dependency or handler fails. The application must define that result; there is no implicit fallback:
import { createFallbackInterceptor } from '@connectum/interceptors';
const fallback = createFallbackInterceptor({
handler: () => ({ items: [] }),
});The example is suitable only for a response whose message fields include items. Use a response that matches your actual contract. With createDefaultInterceptors(), use fallback: { handler } to enable it. fallback: true or an options object without a function handler throws TypeError at construction.
Fallback position
Fallback stands outside circuit breaker and retry, inside timeout and bulkhead. Validation stands directly outside fallback, so a rejected request never reaches the fallback provider. With fallback enabled, the default chain is:
- Retries run first. With
maxRetries: 3, a handler that keeps failing withUnavailableis called four times before fallback supplies the response. A successful retry returns normally without asking fallback. - The breaker sees the failures and can open. An open breaker calls the fallback provider with
Unavailablewithout reaching the service handler. - The provider receives the last failure after retries, or the first failure for a code that retry does not repeat.
CanceledorDeadlineExceededraised by the inner retry also reaches the provider. The client still receives cancellation from the layer that raised it, but provider side effects such as metrics and logs run.- An error thrown by the provider itself is neither retried nor counted by the breaker; it goes straight to error handling.
- With an open breaker, an invalid request gets
InvalidArgumentwhen fallback is enabled. Without fallback, validation stands behind the breaker and the same request getsUnavailablefirst.
When composing manually, put validation before fallback, and fallback before the breaker and retry. An inner fallback would replace the first failure with a value, preventing retries and breaker accounting. A chain without fallback keeps validation after retry.
Access refusals are preserved
Unauthenticated and PermissionDenied are rethrown unchanged and never call the fallback handler, wherever authentication/authorization appears relative to fallback and even when the service handler itself raises the refusal. Other codes and non-ConnectError failures still receive the substitute result.
Place authentication and authorization immediately after error handling, before resilience policies. See manual composition.
Rejected requests are not replaced
Validation failures are client errors. createDefaultInterceptors() places validation before fallback: the caller receives InvalidArgument, and neither the service handler nor the fallback provider runs. Reversing that manual order would replace an invalid request with a fallback value.
What the handler returns
Return a message made with create(OutputSchema, { ... }) or a plain object with that response message's fields. Oneofs use their group name with { case: 'member', value }. The interceptor checks an initializer before constructing the message, because create() alone drops unknown keys and coerces some wrong values.
The first problem fails with a TypeError naming the factory, expected type and field path. It rejects:
undefined,null, a string or an array instead of the object;- unknown keys at any depth and bare oneof member names;
- values of another kind than their field: numbers in string fields, non-boolean bools, non-array repeated fields, enum names instead of numbers, and invalid list or map members;
- numbers the encoder cannot write, including fractions or out-of-range int32 values and
NaN; - non-canonical integer or bool map keys, and non-canonical strings for 64-bit integers: no leading zero,
+, spaces, exponents, hexadecimal,-0or empty strings; bool keys must be"true"or"false"; - lone surrogates anywhere, including nested
Structkeys, andNaNorInfinityinsideStruct; - bytes supplied as
ArrayBuffer,DataViewor arrays with holes or values outside 0 to 255, and oneof objects with keys other thancaseandvalue.
Unknown enum numbers are accepted. Follow protobuf-es message forms: singular wrappers outside a oneof use bare values, and Struct uses plain JSON objects; ready messages are refused in both places. A plain object with another $typeName remains a JSON object in a Struct field. Timestamp, Duration, Value, ListValue, Any, FieldMask and Empty use their own message fields, such as { seconds: 1n }; wrappers in lists or oneofs use objects with value. Behind error handling, invalid provider results reach clients as Internal with internal error; reporting hooks and logs retain the full diagnostic.
Streaming scope
Streaming calls are skipped by default. With skipStreaming: false, an opening failure produces a streaming response whose only message is the fallback value, including the single response of a client-streaming call. A failure while reading an already opened response is not replaced. skipStreaming must be a boolean; invalid types throw TypeError at construction.
Fallback response metadata
For unary RPCs the synthesized response carries only required protocol metadata, on server chains and client transports alike:
content-typematches the request. Connect GET has no such header, so it is derived fromencoding:jsongivesapplication/json,protogivesapplication/proto; otherwise the header stays empty.grpc-status: 0is added as a trailer when the request type starts withapplication/grpc(gRPC and gRPC-web). Other protocols get an empty trailer.
No request headers are copied into this response. Connect, gRPC and gRPC-web clients over HTTP/2 and HTTP/1.1 accept it. Put metadata needed by callers in the returned message. See FallbackOptions.