Batching and single flight
Batching and single flight in the Server functions guide.
The browser stub batches. Calls made in the same tick (one synchronous run of code) to POST functions travel in one request,
POST {base}/_dusk/fn/_batch, as an array of { id, input }. The answer is an array of the same envelopes, in order, so one call
failing (a validation error, a redirect, a 403 from its middleware) rejects that call alone. A batch carries at most 25 calls
(rpc.maxBatchSize); a longer tick is sent as several requests.
Each call runs as it would alone: its own input schema, its own middleware (so a rate limit or an authorization check applies per
call), its own errors. The request-level checks run once for the batch: CSRF, the x-dusk-fn header and the body limit. The
response shares one budget (rpc.maxResponseBytes): a call whose answer would exceed what is left answers
SERVER_FN_RESPONSE_TOO_LARGE, and the others are kept.
Batching is off for a GET function, for a call with its own signal, headers or fetch, and for a function built with
batch: false:
import { serverFn } from "@shadow-js/dusk/server";
// Sent alone, even when other calls are made in the same tick.
export const resetCounter = serverFn({ batch: false }, async () => ({ count: 0 }));
// A read: a GET, never batched.
export const readCounter = serverFn({ method: "GET" }, async () => ({ count: 1 }));
A mutation can also answer with the loader data of the page the browser is on, so the router updates the screen without a second
request. Declare it with singleFlight: true. The browser sends the page's path and query in x-dusk-location; the server runs the
matched loaders for that location under the same request (its cookies and middleware) and sends their data with the answer. The
router applies it (router.applyHydration) before the call resolves, and the loaders are not called again in the browser:
import { serverFn } from "@shadow-js/dusk/server";
export const addTodo = serverFn({ singleFlight: true }, async (input) => {
const title = typeof input === "object" && input !== null && "title" in input ? String(input.title) : "";
return { id: "t1", title };
});
Nothing extra is sent when the location is missing, is not a same-origin path, or leads to a redirect: the browser then loads the page as it would without single flight. A page whose data would exceed the hydration payload limit is also left to the browser.