diff --git a/docs/node/interceptors.md b/docs/node/interceptors.md
index e15c5380..ea98c979 100644
--- a/docs/node/interceptors.md
+++ b/docs/node/interceptors.md
@@ -19,7 +19,7 @@ import { connectNodeAdapter } from "@connectrpc/connect-node";
import type { Interceptor } from "@connectrpc/connect";
const logger: Interceptor = (next) => async (req) => {
- console.log(`recevied message on ${req.url}`);
+ console.log(`received message on ${req.url}`);
return await next(req);
};
@@ -39,7 +39,7 @@ To intercept responses, we simply look at the return value of `next()`:
```ts
const logger: Interceptor = (next) => async (req) => {
- console.log(`recevied message on ${req.url}`);
+ console.log(`received message on ${req.url}`);
const res = await next(req);
if (!res.stream) {
console.log("message:", res.message);
@@ -231,4 +231,4 @@ await server.listen({
});
```
-The request passed to the `contextValues` function is different for each server plugin, please refer to the documentation for the server plugin you are using.
\ No newline at end of file
+The request passed to the `contextValues` function is different for each server plugin, please refer to the documentation for the server plugin you are using.
diff --git a/docs/node/server-plugins.md b/docs/node/server-plugins.md
index 0a430948..63112e01 100644
--- a/docs/node/server-plugins.md
+++ b/docs/node/server-plugins.md
@@ -25,7 +25,7 @@ http.createServer(
).listen(8080);
```
-The function accepts all common options, and the following additional
+The function accepts all [common options](#common-options), and the following additional
ones:
- `fallback?: NodeHandlerFn`
@@ -75,7 +75,13 @@ await server.listen({
});
```
-The plugin accepts all common options, and the following additional ones:
+The plugin accepts all [common options](#common-options), and the following additional ones:
+- `shutdownTimeoutMs?: number`
+ If set, the server will wait for the specified duration before aborting any
+ in-flight requests once [`fastify.close`](https://fastify.dev/docs/latest/Reference/Server/#close) is called.
+- `shutdownError?: unknown`
+ The reason to use when shutdown occurs. Note that if this is a `ConnectError` it will
+ be sent to the client.
- `contextValues?: (req: FastifyRequest) => ContextValues`
A function that returns a set of context values for each request. The
context values are passed to the service implementation. See
@@ -112,9 +118,14 @@ This file is a Next.js [catch-all API route](https://nextjs.org/docs/routing/dyn
serve your Connect RPCs with the `/api` prefix. Make sure to include the `/api` prefix in the `baseUrl` option for
your client transport.
-The middleware accepts all common options, and the following additional
+The middleware accepts all [common options](#common-options), and the following additional
one:
+- `prefix?: string`
+ Serve all handlers under this prefix. For example, the prefix "/something"
+ will serve the RPC foo.FooService/Bar under "/something/foo.FooService/Bar".
+ By default, this is `/api` for Next.js.
+ Note that many gRPC client implementations do not allow for prefixes.
- `contextValues?: (req: NextApiRequest) => ContextValues`
A function that returns a set of context values for each request. The
context values are passed to the service implementation. See
@@ -150,13 +161,13 @@ app.use(expressConnectMiddleware({
http.createServer(app).listen(8080);
```
-The middleware accepts all common options, and the following additional
+The middleware accepts all [common options](#common-options), and the following additional
one:
- `requestPathPrefix?: string`
-Serve all handlers under this prefix. For example, the prefix "/something"
-will serve the RPC foo.FooService/Bar under "/something/foo.FooService/Bar".
-Note that many gRPC client implementations do not allow for prefixes.
+ Serve all handlers under this prefix. For example, the prefix "/something"
+ will serve the RPC foo.FooService/Bar under "/something/foo.FooService/Bar".
+ Note that many gRPC client implementations do not allow for prefixes.
- `contextValues?: (req: express.Request) => ContextValues`
A function that returns a set of context values for each request. The
context values are passed to the service implementation. See
@@ -176,10 +187,14 @@ All adapters take a set of common options:
- `routes: (router: ConnectRouter) => void`
The adapter will call this function, and lets you register your services.
See [Implementing services](./implementing-services.md) for an example.
+- `maxTimeoutMs?: number`
+ The maximum value for [timeouts](./timeouts) that clients may specify.
+ If a client requests a timeout that is greater than `maxTimeoutMs`,
+ the server responds with the error code `invalid_argument`.
- `connect?: boolean`
Whether to enable the Connect protocol for your routes. Enabled by default.
- `grpcWeb?: boolean`
- Whether to enable the gRPC protocol for your routes. Enabled by default.
+ Whether to enable the gRPC-web protocol for your routes. Enabled by default.
- `grpc?: boolean`
Whether to enable the gRPC protocol for your routes. Enabled by default.
- `interceptors?: Interceptor[]`
diff --git a/docs/node/testing.md b/docs/node/testing.md
index 5bd0d5e3..404d5461 100644
--- a/docs/node/testing.md
+++ b/docs/node/testing.md
@@ -1,6 +1,6 @@
---
title: Testing
-sidebar_position: 6
+sidebar_position: 7
---
When writing tests for your Connect for Node.js application, your approach will
diff --git a/docs/node/timeouts.md b/docs/node/timeouts.md
new file mode 100644
index 00000000..a059192d
--- /dev/null
+++ b/docs/node/timeouts.md
@@ -0,0 +1,84 @@
+---
+title: Timeouts
+sidebar_position: 8
+---
+
+Timeouts can be used to limit the time a server may take to process a response.
+In Connect-ES, timeout values are set by the client via the `timeoutMs` option
+when issuing a requests. If handling the response takes longer than the timeout,
+they will respond with the error code `deadline_exceeded`. In gRPC, the concept
+is also known as [deadlines](https://grpc.io/docs/guides/deadlines/).
+
+## Using `HandlerContext`
+
+Servers can interact with this timeout via the [handler context](https://connectrpc.com/docs/node/implementing-services#context).
+Depending on your needs, there are a few ways to approach it:
+
+The first way is via an [`AbortSignal`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal)
+on the context. Using this signal, route handlers can then tell if the timeout specified
+by the client was reached and abort their processes accordingly. The `AbortSignal` can be found
+via the property name `signal`.
+
+The signal can be passed to other functions or used to gracefully stop processes when the timeout is reached.
+Using `signal` works for any operation you might want to call as long as the API supports it.
+
+```ts
+import type { HandlerContext } from "@bufbuild/connect";
+
+const say = async (req: SayRequest, ctx: HandlerContext) => {
+
+ ctx.signal.aborted; // true if timed out
+ ctx.signal.reason; // an error with code deadline_exceeded if timed out
+
+ // raises an error with code deadline_exceeded if timed out
+ ctx.signal.throwIfAborted();
+
+ // the AbortSignal can be passed to other functions
+ await longRunning(ctx.signal);
+
+ return new SayResponse({sentence: `You said: ${req.sentence}`});
+};
+```
+
+A second way to interact with the timeout value is via the `timeoutMs()` function
+on the handler context. If the current request has a timeout, this function
+returns the remaining time.
+
+Using the `timeoutMs()` function is preferable when invoking upstream RPC calls
+because it is more efficient and robust - you have a guarantee that the peer is
+aware of the deadline, regardless of network issues. In gRPC, the concept is also
+known as [deadline propagation](https://grpc.io/docs/guides/deadlines/#deadline-propagation).
+
+```ts
+import type { HandlerContext } from "@bufbuild/connect";
+
+const say = async (req: SayRequest, ctx: HandlerContext) => {
+
+ // If a timeout was set on the call to this service, the timeoutMs() method
+ // returns the remaining time in milliseconds.
+
+ // Passing the value to an upstream client call propagates the timeout.
+ await upstreamClient.someCall({}, { timeoutMs: ctx.timeoutMs() });
+
+ return new SayResponse({sentence: `You said: ${req.sentence}`});
+};
+```
+
+In addition, to server-side support for timeouts, there is also a related option on `ConnectRouter`
+that helps constraining timeout values: `maxTimeoutMs`. For an explanation of this option,
+see the docs on [Server Plugins](server-plugins#common-options)
+
+Also note that while this page discusses timeouts in the context of a server, Connect-ES clients
+honor timeout values and will raise a `ConnectError` with code `DeadlineExceeded`. Even if a connection
+becomes unresponsive, the client call will still abort at the configured timeout.
+
+```ts
+try {
+ // If this call takes more than 200 milliseconds, it is canceled
+ await client.say({sentence: "Hello"}, { timeoutMs: 200 });
+} catch (err) {
+ if (err instanceof ConnectError && err.code === Code.DeadlineExceeded) {
+ // handle the timeout error
+ }
+}
+```