Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 12 additions & 9 deletions en/guide/interceptors.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 },
});
Expand All @@ -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

Expand Down
64 changes: 51 additions & 13 deletions en/guide/interceptors/built-in.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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).
Expand Down Expand Up @@ -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({
Expand Down
6 changes: 3 additions & 3 deletions en/guide/production/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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,
Expand Down
20 changes: 9 additions & 11 deletions en/guide/quickstart.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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 },
});
```

Expand Down
12 changes: 6 additions & 6 deletions en/guide/service-communication.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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) |

Expand Down
8 changes: 4 additions & 4 deletions en/guide/service-communication/client-interceptors.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand All @@ -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,
}),
],
Expand All @@ -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,
}),
],
Expand Down
2 changes: 1 addition & 1 deletion en/guide/validation.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion en/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z"/></svg>'
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: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="18" height="11" x="3" y="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg>'
title: Auth & Zero-Trust
details: JWT, gateway, and session authentication with declarative RBAC. mTLS and proto-based authorization keep security alongside your API contract.
Expand Down
Loading
Loading