diff --git a/en/guide/interceptors.md b/en/guide/interceptors.md
index 95898e9..68f92c4 100644
--- a/en/guide/interceptors.md
+++ b/en/guide/interceptors.md
@@ -16,9 +16,10 @@ import routes from '#gen/routes.js';
const server = createServer({
services: [routes],
port: 5000,
+ // errorHandler + validation by default; resilience is opt-in
interceptors: createDefaultInterceptors({
- timeout: { duration: 10_000 },
- retry: { maxRetries: 5 },
+ timeout: { duration: 10_000 }, // explicitly enabled
+ retry: { maxRetries: 5 }, // explicitly enabled
}),
shutdown: { autoShutdown: true },
});
@@ -39,18 +40,20 @@ Response <- interceptor1 <- interceptor2 <- ... <- handler
### Built-in Chain
-`createDefaultInterceptors()` provides 8 production-ready interceptors in a fixed order:
+`createDefaultInterceptors()` is a chain factory for 8 production-ready interceptors in a fixed order:
| # | Interceptor | Purpose | Default |
|---|-------------|---------|---------|
| 1 | **errorHandler** | Normalizes errors into ConnectError | Enabled |
-| 2 | **timeout** | Limits request execution time | Enabled (30s) |
-| 3 | **bulkhead** | Limits concurrent requests | Enabled (capacity 10, queue 10) |
-| 4 | **circuitBreaker** | Prevents cascading failures | Enabled (threshold 5) |
-| 5 | **retry** | Retries transient failures with exponential backoff | Enabled (3 retries) |
-| 6 | **fallback** | Graceful degradation | Disabled |
+| 2 | **timeout** | Limits request execution time | **Opt-in** (30s when enabled) |
+| 3 | **bulkhead** | Limits concurrent requests | **Opt-in** (capacity 10, queue 10 when enabled) |
+| 4 | **circuitBreaker** | Prevents cascading failures (outbound pattern) | **Opt-in** (threshold 5 when enabled) |
+| 5 | **retry** | Retries transient failures with exponential backoff | **Opt-in** (3 retries when enabled) |
+| 6 | **fallback** | Graceful degradation | **Opt-in** (requires a handler) |
| 7 | **validation** | Validates via @connectrpc/validate | Enabled |
-| 8 | **serializer** | JSON serialization for protobuf | **Disabled** |
+| 8 | **serializer** | JSON serialization for protobuf | **Opt-in** |
+
+**No hidden behavioral logic.** Only structural interceptors (errorHandler, validation) are enabled by default. Resilience interceptors (timeout, bulkhead, circuitBreaker, retry) alter request behavior and must be enabled explicitly with `true` or an options object.
### Per-Method Routing
diff --git a/en/guide/interceptors/built-in.md b/en/guide/interceptors/built-in.md
index e585b02..9787178 100644
--- a/en/guide/interceptors/built-in.md
+++ b/en/guide/interceptors/built-in.md
@@ -4,7 +4,7 @@ outline: deep
# Built-in Interceptors
-Connectum provides 8 production-ready interceptors via `createDefaultInterceptors()`. They form a fixed chain that covers error handling, resilience, validation, and serialization.
+Connectum provides 8 production-ready interceptors via `createDefaultInterceptors()`. They form a fixed-order chain that covers error handling, resilience, validation, and serialization.
## The Default Chain
@@ -15,15 +15,54 @@ errorHandler -> timeout -> bulkhead -> circuitBreaker -> retry -> fallback -> va
| # | Interceptor | Purpose | Default |
|---|-------------|---------|---------|
| 1 | **errorHandler** | Normalizes errors into `ConnectError` | Enabled |
-| 2 | **timeout** | Limits request execution time | Enabled (30s) |
-| 3 | **bulkhead** | Limits concurrent requests | Enabled (capacity 10, queue 10) |
-| 4 | **circuitBreaker** | Prevents cascading failures | Enabled (threshold 5) |
-| 5 | **retry** | Retries transient failures with exponential backoff | Enabled (3 retries) |
-| 6 | **fallback** | Graceful degradation | Disabled |
+| 2 | **timeout** | Limits request execution time | **Opt-in** (30s when enabled) |
+| 3 | **bulkhead** | Limits concurrent requests | **Opt-in** (capacity 10, queue 10 when enabled) |
+| 4 | **circuitBreaker** | Prevents cascading failures (outbound pattern, see below) | **Opt-in** (threshold 5 when enabled) |
+| 5 | **retry** | Retries transient failures with exponential backoff | **Opt-in** (3 retries when enabled) |
+| 6 | **fallback** | Graceful degradation | **Opt-in** (requires a handler) |
| 7 | **validation** | Validates via `@connectrpc/validate` | Enabled |
-| 8 | **serializer** | JSON serialization for protobuf | **Disabled** |
+| 8 | **serializer** | JSON serialization for protobuf | **Opt-in** |
-The order is deliberate: `errorHandler` is outermost (catches everything), `serializer` is innermost (closest to the handler).
+The order is deliberate: `errorHandler` is outermost (catches everything), `serializer` is innermost (closest to the handler). The order applies to whichever interceptors you enable. In particular, `circuitBreaker` wraps `retry`, so one logical request increments the failure counter at most once regardless of retry attempts.
+
+::: warning No hidden behavioral logic
+Only structural interceptors (errorHandler, validation) are enabled by default. Resilience interceptors (timeout, bulkhead, circuitBreaker, retry) alter request behavior and must be enabled explicitly with `true` or an options object — implicitly enabled resilience caused a confirmed production incident (a server-side circuit breaker tripped by expected business errors).
+:::
+
+## Circuit Breaker: Placement and Error Classification
+
+The circuit breaker is an **outbound/client-side pattern**: it protects the caller from a sick upstream (fail fast instead of waiting on timeouts) and gives that upstream room to recover. On a server's inbound stack it degenerates into error-rate load shedding — for inbound protection prefer explicit `timeout` + `bulkhead`.
+
+```typescript
+// Recommended: circuit breaker on an outbound client transport
+import { createConnectTransport } from '@connectrpc/connect-node';
+import { createCircuitBreakerInterceptor } from '@connectum/interceptors';
+
+const transport = createConnectTransport({
+ baseUrl: 'http://upstream:5000',
+ interceptors: [
+ createCircuitBreakerInterceptor({ threshold: 5, halfOpenAfter: 30_000 }),
+ ],
+});
+```
+
+**Error classification.** By default only infrastructure errors count as circuit failures: `Unknown`, `DeadlineExceeded`, `Internal`, `Unavailable`, `DataLoss`, `ResourceExhausted` (plus any non-`ConnectError` thrown value). Business codes (`invalid_argument`, `not_found`, `failed_precondition`, `already_exists`, ...) are expected responses of a healthy service: they never open the breaker, and in half-open state they close it.
+
+Customize with `failurePredicate(error, defaultPredicate)` — the default predicate (exported as `defaultFailurePredicate`) is passed in for composition:
+
+```typescript
+import { Code, ConnectError } from '@connectrpc/connect';
+import { createCircuitBreakerInterceptor } from '@connectum/interceptors';
+
+// Exclude upstream per-client rate limits from tripping the breaker
+createCircuitBreakerInterceptor({
+ failurePredicate: (err, def) =>
+ def(err) && !(err instanceof ConnectError && err.code === Code.ResourceExhausted),
+});
+
+// Restore legacy behavior (every error trips the breaker)
+createCircuitBreakerInterceptor({ failurePredicate: () => true });
+```
::: tip When to enable the serializer
Enable the serializer when your service uses the **Connect protocol** (HTTP/1.1 JSON) and you need automatic protobuf ↔ JSON conversion. Not needed for pure **gRPC** services (binary protobuf format).
@@ -74,16 +113,15 @@ await server.start();
## Customizing the Default Chain
-Pass options to `createDefaultInterceptors()` to customize individual interceptors. Set an interceptor to `false` to disable it entirely:
+Pass options to `createDefaultInterceptors()` to customize individual interceptors. Pass `true` or an options object to enable an opt-in interceptor; set one of the default-enabled interceptors to `false` to disable it:
```typescript
import { createDefaultInterceptors } from '@connectum/interceptors';
const interceptors = createDefaultInterceptors({
- timeout: { duration: 10_000 }, // Custom timeout (10s instead of 30s)
- retry: false, // Disable retry
- bulkhead: { capacity: 20, queueSize: 20 }, // Higher concurrency limits
- // All others remain at defaults
+ timeout: { duration: 10_000 }, // Enable timeout (10s)
+ bulkhead: { capacity: 20, queueSize: 20 }, // Enable bulkhead with custom limits
+ // errorHandler and validation remain enabled by default
});
const server = createServer({
diff --git a/en/guide/production/architecture.md b/en/guide/production/architecture.md
index 94f1859..2e32c74 100644
--- a/en/guide/production/architecture.md
+++ b/en/guide/production/architecture.md
@@ -349,7 +349,7 @@ async createOrder(req) {
### Circuit Breaker at Application Level
-Connectum's built-in interceptor chain includes a circuit breaker. For inter-service calls, configure it per-client:
+Connectum provides a circuit breaker interceptor (opt-in — it is not enabled by default). It is an outbound/client-side pattern: enable it per-client for inter-service calls; for server inbound protection prefer explicit `timeout` + `bulkhead`:
```typescript
import { createDefaultInterceptors } from '@connectum/interceptors';
@@ -358,8 +358,8 @@ const transport = createGrpcTransport({
baseUrl: 'http://inventory-service:5000',
httpVersion: '2',
interceptors: createDefaultInterceptors({
- circuitBreaker: { failureThreshold: 5 },
- timeout: { timeoutMs: 5000 },
+ circuitBreaker: { threshold: 5 },
+ timeout: { duration: 5000 },
retry: { maxRetries: 2 },
// Disable server-side-only interceptors
bulkhead: false,
diff --git a/en/guide/quickstart.md b/en/guide/quickstart.md
index fa8fb12..93286c6 100644
--- a/en/guide/quickstart.md
+++ b/en/guide/quickstart.md
@@ -254,15 +254,13 @@ curl http://localhost:5000/healthz
| Feature | Details |
|---------|---------|
| **Error handling** | Automatic error normalization to gRPC status codes |
-| **Timeout** | 30s default per request |
-| **Bulkhead** | Max 10 concurrent requests + 10-item queue |
-| **Circuit breaker** | Opens after 5 consecutive failures |
-| **Retry** | 3 retries with exponential backoff |
| **Validation** | Proto constraint validation via [@connectrpc/validate](https://github.com/connectrpc/validate-es) |
| **Health checks** | gRPC + HTTP endpoints |
| **Reflection** | Runtime service discovery |
| **Graceful shutdown** | SIGTERM/SIGINT with connection draining |
+Resilience interceptors (timeout, bulkhead, circuit breaker, retry) are **opt-in** — enable them explicitly via `createDefaultInterceptors()` options. See [Step 12](#_12-built-in-interceptors).
+
---
The following steps show how to extend your base service with additional framework features.
@@ -377,24 +375,24 @@ See [Graceful Shutdown](/en/guide/server/graceful-shutdown) for dependency graph
## 12. Built-in Interceptors
-`createDefaultInterceptors()` assembles 8 interceptors in a fixed order:
+`createDefaultInterceptors()` assembles up to 8 interceptors in a fixed order. Only errorHandler and validation are enabled by default — resilience interceptors are opt-in (no hidden behavioral logic):
| # | Interceptor | Default | Purpose |
|---|-------------|---------|---------|
| 1 | **errorHandler** | on | Normalize errors to gRPC status codes |
-| 2 | **timeout** | 30s | Enforce per-request deadline |
-| 3 | **bulkhead** | 10/10 | Limit concurrent requests + queue |
-| 4 | **circuitBreaker** | 5 failures | Prevent cascading failures |
-| 5 | **retry** | 3 retries | Exponential backoff for transients |
+| 2 | **timeout** | opt-in (30s when enabled) | Enforce per-request deadline |
+| 3 | **bulkhead** | opt-in (10/10 when enabled) | Limit concurrent requests + queue |
+| 4 | **circuitBreaker** | opt-in (5 failures when enabled) | Prevent cascading failures (outbound pattern) |
+| 5 | **retry** | opt-in (3 retries when enabled) | Exponential backoff for transients |
| 6 | **fallback** | off | Graceful degradation (requires handler) |
| 7 | **validation** | on | Proto constraint validation |
| 8 | **serializer** | off | JSON serialization (opt-in for Connect protocol) |
```typescript
+// Enable resilience interceptors explicitly (true or an options object)
const interceptors = createDefaultInterceptors({
- retry: false,
timeout: { duration: 10_000 },
- bulkhead: { maxConcurrent: 20, maxQueue: 50 },
+ bulkhead: { capacity: 20, queueSize: 50 },
});
```
diff --git a/en/guide/service-communication.md b/en/guide/service-communication.md
index 90fa22a..32e2fd0 100644
--- a/en/guide/service-communication.md
+++ b/en/guide/service-communication.md
@@ -27,7 +27,7 @@ const inventoryTransport = createGrpcTransport({
serverPort: Number(process.env.INVENTORY_PORT),
}),
...createDefaultInterceptors({
- circuitBreaker: { failureThreshold: 5 },
+ circuitBreaker: { threshold: 5 },
timeout: { duration: 5_000 },
retry: { maxRetries: 2 },
// Disable server-only interceptors
@@ -69,11 +69,11 @@ Not all server interceptors are appropriate for client transports. When using `c
| Interceptor | Server | Client | Notes |
|-------------|:------:|:------:|-------|
| **errorHandler** | Yes | No | Normalizes errors for responses -- not needed on client |
-| **timeout** | Yes | Yes | Enforce per-request deadline |
-| **bulkhead** | Yes | No | Limits server concurrency -- not applicable to clients |
-| **circuitBreaker** | Yes | Yes | Prevents cascading failures to downstream services |
-| **retry** | Yes | Yes | Retries transient errors |
-| **fallback** | Yes | No | Requires server-side handler |
+| **timeout** | Opt-in | Opt-in | Enforce per-request deadline |
+| **bulkhead** | Opt-in | No | Limits server concurrency -- not applicable to clients |
+| **circuitBreaker** | Opt-in | Opt-in | Outbound pattern -- prevents cascading failures to downstream services |
+| **retry** | Opt-in | Opt-in | Retries transient errors |
+| **fallback** | Opt-in | No | Requires server-side handler |
| **validation** | Yes | No | Validates incoming requests -- not outgoing |
| **serializer** | Opt-in | No | Server-side JSON serialization (disabled by default; enable for Connect protocol) |
diff --git a/en/guide/service-communication/client-interceptors.md b/en/guide/service-communication/client-interceptors.md
index 80e82e4..ff2882e 100644
--- a/en/guide/service-communication/client-interceptors.md
+++ b/en/guide/service-communication/client-interceptors.md
@@ -131,7 +131,7 @@ const transport = createGrpcTransport({
serverPort: 5000,
}),
...createDefaultInterceptors({
- circuitBreaker: { failureThreshold: 5 },
+ circuitBreaker: { threshold: 5 },
timeout: { duration: 5_000 },
retry: { maxRetries: 2 },
// Disable server-only interceptors
@@ -154,7 +154,7 @@ The circuit breaker tracks consecutive failures per client transport:
| **Open** | Requests fail immediately with `Unavailable` (no downstream call) |
| **Half-Open** | A single probe request is allowed; success closes, failure re-opens |
-The default `failureThreshold` is 5 consecutive failures. After the circuit opens, it automatically transitions to half-open after a cooldown period.
+The default `threshold` is 5 consecutive failures. After the circuit opens, it automatically transitions to half-open after a cooldown period.
### Per-Service Configuration
@@ -170,7 +170,7 @@ const paymentTransport = createGrpcTransport({
...createDefaultInterceptors({
timeout: { duration: 3_000 },
retry: { maxRetries: 3 },
- circuitBreaker: { failureThreshold: 3 },
+ circuitBreaker: { threshold: 3 },
bulkhead: false, errorHandler: false, serializer: false, validation: false,
}),
],
@@ -185,7 +185,7 @@ const recommendationTransport = createGrpcTransport({
...createDefaultInterceptors({
timeout: { duration: 10_000 },
retry: { maxRetries: 1 },
- circuitBreaker: { failureThreshold: 10 },
+ circuitBreaker: { threshold: 10 },
bulkhead: false, errorHandler: false, serializer: false, validation: false,
}),
],
diff --git a/en/guide/validation.md b/en/guide/validation.md
index 05e1577..619bf98 100644
--- a/en/guide/validation.md
+++ b/en/guide/validation.md
@@ -18,7 +18,7 @@ Client → errorHandler → ... → validation → serializer → Handler
Invalid: INVALID_ARGUMENT
```
-Validation runs as the 7th interceptor in the default chain (before serializer, after resilience interceptors). Invalid requests are rejected with `INVALID_ARGUMENT` before reaching the handler.
+Validation runs as the 7th interceptor in the default chain (before serializer, after any explicitly enabled resilience interceptors). Invalid requests are rejected with `INVALID_ARGUMENT` before reaching the handler.
## Setup
diff --git a/en/index.md b/en/index.md
index d896dae..e553dda 100644
--- a/en/index.md
+++ b/en/index.md
@@ -25,7 +25,7 @@ features:
details: Opinionated production architecture with a single createServer() entry point. Predictable behavior across all your services — no glue code, no custom wiring.
- icon: ''
title: Operational Safety Envelope
- details: Fixed-order interceptor chain — timeout, retry, circuit breaker, bulkhead, and fallback. Enterprise resilience patterns enforced by default.
+ details: Fixed-order interceptor chain — timeout, retry, circuit breaker, bulkhead, and fallback. Explicit, opt-in resilience patterns with no hidden behavioral logic.
- icon: ''
title: Auth & Zero-Trust
details: JWT, gateway, and session authentication with declarative RBAC. mTLS and proto-based authorization keep security alongside your API contract.
diff --git a/en/migration/index.md b/en/migration/index.md
index 4864224..1d6eb52 100644
--- a/en/migration/index.md
+++ b/en/migration/index.md
@@ -7,6 +7,58 @@ description: Migration guides and breaking changes for Connectum releases
This page covers breaking changes and migration steps between Connectum releases.
+## BREAKING: Resilience interceptors are now opt-in in `createDefaultInterceptors()`
+
+> Applies to the next release on top of RC.10.
+
+`createDefaultInterceptors()` now enables only the structural interceptors — **errorHandler** and **validation**. The resilience interceptors (**timeout**, **bulkhead**, **circuitBreaker**, **retry**) are **opt-in**: enable each one explicitly with `true` or an options object.
+
+**Why**: no hidden behavioral logic. Implicitly enabled resilience caused a confirmed production incident — a server-side circuit breaker was tripped by expected business errors (such as `invalid_argument`) and started rejecting healthy traffic.
+
+| Interceptor | Before | After |
+|-------------|--------|-------|
+| errorHandler | enabled | enabled (unchanged) |
+| timeout | enabled (30s) | **opt-in** (30s when enabled) |
+| bulkhead | enabled (10/10) | **opt-in** (10/10 when enabled) |
+| circuitBreaker | enabled (5 failures) | **opt-in** (5 failures when enabled) |
+| retry | enabled (3 attempts) | **opt-in** (3 attempts when enabled) |
+| fallback | disabled | opt-in (unchanged) |
+| validation | enabled | enabled (unchanged) |
+| serializer | disabled | opt-in (unchanged) |
+
+**Migration**:
+
+```typescript
+// Before — timeout, bulkhead, circuitBreaker, retry were implicitly active
+createDefaultInterceptors()
+
+// After — to keep the previous behavior, enable them explicitly
+createDefaultInterceptors({
+ timeout: true,
+ bulkhead: true,
+ circuitBreaker: true,
+ retry: true,
+})
+
+// After — if you only need errorHandler + validation (no change needed)
+createDefaultInterceptors()
+```
+
+Code that already passes explicit options (`timeout: { duration: 10_000 }`, `retry: false`, ...) keeps working: an options object or `true` means enabled, `false` means disabled.
+
+### Circuit breaker: error classification and placement
+
+The circuit breaker now classifies errors. By default only infrastructure errors count as circuit failures: `Unknown`, `DeadlineExceeded`, `Internal`, `Unavailable`, `DataLoss`, `ResourceExhausted` (plus any non-`ConnectError` thrown value). Business codes (`invalid_argument`, `not_found`, ...) never open the breaker, and in half-open state they close it.
+
+Customize via the new `failurePredicate(error, defaultPredicate)` option; the default classifier is exported as `defaultFailurePredicate`:
+
+```typescript
+// Restore legacy behavior (every error trips the breaker)
+createCircuitBreakerInterceptor({ failurePredicate: () => true });
+```
+
+The circuit breaker is repositioned as an **outbound/client-transport pattern**. For server inbound protection prefer explicit `timeout` + `bulkhead`. See [@connectum/interceptors](/en/packages/interceptors#circuit-breaker) for details.
+
## Minimum Node.js raised to 22.13.0
> Applies to the next release on top of RC.10.
@@ -381,7 +433,7 @@ await server.start();
### Interceptors: No Auto-Defaults
-Starting from v1.0.0-beta.x, `@connectum/core` has **zero internal dependencies**. Omitting the `interceptors` option (or passing `[]`) means **no interceptors are applied**. To use the default resilience chain, explicitly pass `createDefaultInterceptors()`:
+Starting from v1.0.0-beta.x, `@connectum/core` has **zero internal dependencies**. Omitting the `interceptors` option (or passing `[]`) means **no interceptors are applied**. To use the default interceptor chain, explicitly pass `createDefaultInterceptors()`:
```typescript
import { createServer } from '@connectum/core';
diff --git a/en/packages/interceptors.md b/en/packages/interceptors.md
index 92ebf80..c3dcd12 100644
--- a/en/packages/interceptors.md
+++ b/en/packages/interceptors.md
@@ -33,13 +33,13 @@ pnpm add @connectum/interceptors
```typescript
import { createDefaultInterceptors } from '@connectum/interceptors';
-// All defaults (fallback disabled)
+// Defaults: errorHandler + validation only (resilience is opt-in)
const interceptors = createDefaultInterceptors();
-// Custom configuration
+// Explicitly enable resilience interceptors
const interceptors = createDefaultInterceptors({
- retry: false,
timeout: { duration: 10_000 },
+ retry: true,
circuitBreaker: { threshold: 3 },
});
```
@@ -54,8 +54,7 @@ const server = createServer({
services: [routes],
interceptors: createDefaultInterceptors({
errorHandler: { logErrors: true },
- timeout: { duration: 15_000 },
- retry: false,
+ timeout: { duration: 15_000 }, // explicitly enabled
}),
});
```
@@ -71,13 +70,15 @@ errorHandler -> timeout -> bulkhead -> circuitBreaker -> retry -> fallback -> va
| # | Interceptor | Purpose | Default |
|---|-------------|---------|---------|
| 1 | **errorHandler** | Catch-all error normalization (outermost) | Enabled |
-| 2 | **timeout** | Enforce request deadline | Enabled (30s) |
-| 3 | **bulkhead** | Limit concurrent requests | Enabled (10/10) |
-| 4 | **circuitBreaker** | Prevent cascading failures | Enabled (5 failures) |
-| 5 | **retry** | Retry transient failures with backoff | Enabled (3 retries) |
-| 6 | **fallback** | Graceful degradation | **Disabled** |
+| 2 | **timeout** | Enforce request deadline | **Opt-in** (30s when enabled) |
+| 3 | **bulkhead** | Limit concurrent requests | **Opt-in** (10/10 when enabled) |
+| 4 | **circuitBreaker** | Prevent cascading failures (outbound pattern) | **Opt-in** (5 failures when enabled) |
+| 5 | **retry** | Retry transient failures with backoff | **Opt-in** (3 retries when enabled) |
+| 6 | **fallback** | Graceful degradation | **Opt-in** (requires a handler) |
| 7 | **validation** | Validate request messages (@connectrpc/validate) | Enabled |
-| 8 | **serializer** | JSON serialization (innermost) | **Disabled** |
+| 8 | **serializer** | JSON serialization (innermost) | **Opt-in** |
+
+**No hidden behavioral logic.** Only structural interceptors (errorHandler, validation) are enabled by default. Resilience interceptors (timeout, bulkhead, circuitBreaker, retry) alter request behavior and must be enabled explicitly with `true` or an options object — implicitly enabled resilience caused a confirmed production incident (a server-side circuit breaker tripped by expected business errors).
## API Reference
@@ -96,13 +97,13 @@ Each interceptor can be set to `true` (defaults), `false` (disabled), or an opti
```typescript
interface DefaultInterceptorOptions {
errorHandler?: boolean | ErrorHandlerOptions; // default: true
- timeout?: boolean | TimeoutOptions; // default: true
- bulkhead?: boolean | BulkheadOptions; // default: true
- circuitBreaker?: boolean | CircuitBreakerOptions; // default: true
- retry?: boolean | RetryOptions; // default: true
- fallback?: boolean | FallbackOptions; // default: false
+ timeout?: boolean | TimeoutOptions; // default: false (opt-in)
+ bulkhead?: boolean | BulkheadOptions; // default: false (opt-in)
+ circuitBreaker?: boolean | CircuitBreakerOptions; // default: false (opt-in)
+ retry?: boolean | RetryOptions; // default: false (opt-in)
+ fallback?: boolean | FallbackOptions; // default: false (opt-in)
validation?: boolean; // default: true
- serializer?: boolean | SerializerOptions; // default: false
+ serializer?: boolean | SerializerOptions; // default: false (opt-in)
}
```
@@ -169,6 +170,8 @@ const interceptor = createBulkheadInterceptor({
Prevents cascading failures by breaking the circuit on consecutive errors.
+The circuit breaker is an **outbound/client-side pattern**: it protects the caller from a sick upstream and gives that upstream room to recover. On a server's inbound stack it degenerates into error-rate load shedding — for inbound protection prefer explicit `timeout` + `bulkhead`. When enabled in the default chain, the breaker wraps `retry`, so one logical request increments the failure counter at most once regardless of retry attempts.
+
```typescript
import { createCircuitBreakerInterceptor } from '@connectum/interceptors';
@@ -184,6 +187,25 @@ const interceptor = createCircuitBreakerInterceptor({
| `threshold` | `number` | `5` | Consecutive failures before opening |
| `halfOpenAfter` | `number` | `30000` | Milliseconds before half-open attempt |
| `skipStreaming` | `boolean` | `true` | Skip for streaming calls |
+| `failurePredicate` | `(error: unknown, defaultPredicate: (error: unknown) => boolean) => boolean` | `defaultFailurePredicate` | Decides which errors count as circuit failures; receives the default predicate for composition |
+
+**Error classification.** By default only infrastructure errors count as circuit failures: `Unknown`, `DeadlineExceeded`, `Internal`, `Unavailable`, `DataLoss`, `ResourceExhausted` (plus any non-`ConnectError` thrown value). Business codes (`invalid_argument`, `not_found`, `failed_precondition`, `already_exists`, ...) never open the breaker, and in half-open state they close it (the upstream answered — it is alive). The default classifier is exported as `defaultFailurePredicate`.
+
+```typescript
+import { Code, ConnectError } from '@connectrpc/connect';
+import { createCircuitBreakerInterceptor } from '@connectum/interceptors';
+
+// Exclude upstream per-client rate limits from tripping the breaker
+createCircuitBreakerInterceptor({
+ failurePredicate: (err, def) =>
+ def(err) && !(err instanceof ConnectError && err.code === Code.ResourceExhausted),
+});
+
+// Restore legacy behavior (every error trips the breaker)
+createCircuitBreakerInterceptor({ failurePredicate: () => true });
+```
+
+A predicate that throws is fail-closed: the error counts as a failure and the original upstream error is propagated to the caller.
### Retry
@@ -320,6 +342,7 @@ type MethodFilterMap = Record;
| `createSerializerInterceptor` | JSON serialization |
| `createRetryInterceptor` | Retry with backoff |
| `createCircuitBreakerInterceptor` | Circuit breaker |
+| `defaultFailurePredicate` | Default circuit breaker error classifier (infrastructure codes only) |
| `createTimeoutInterceptor` | Request timeout |
| `createBulkheadInterceptor` | Concurrency limiter |
| `createFallbackInterceptor` | Graceful degradation |