Use a standard AbortController to cancel a specific request. Pass the signal via the call() setup object. Aborted requests return an error with type === 'request-aborted'.
import {initClient} from '@mionjs/client';
import type {MyApi} from './server.routes.ts';
const {routes} = initClient<MyApi>({baseURL: 'http://localhost:3000'});
// create an AbortController for this request
const controller = new AbortController();
// pass the signal via call setup
const resultPromise = routes.users.sayHello({id: '1', name: 'John', surname: 'Doe'}).call({signal: controller.signal});
// cancel the request (e.g. on component unmount or user action)
controller.abort();
const [result, error] = await resultPromise;
// error.type === 'request-aborted'
Set a timeout in milliseconds to automatically cancel a request if it takes too long. Timed-out requests return an error with type === 'request-timeout'.
import {initClient} from '@mionjs/client';
import type {MyApi} from './server.routes.ts';
const {routes} = initClient<MyApi>({baseURL: 'http://localhost:3000'});
// this request will fail if it takes longer than 5 seconds
const [result, error] = await routes.users.sayHello({id: '1', name: 'John', surname: 'Doe'}).call({timeout: 5000});
if (error?.type === 'request-timeout') {
console.log('Request took too long');
}
Set a default timeout for all requests via initClient. Per-request timeouts override the client default.
import {initClient} from '@mionjs/client';
import type {MyApi} from './server.routes.ts';
// all requests timeout after 10 seconds unless overridden per-request
const {routes} = initClient<MyApi>({
baseURL: 'http://localhost:3000',
timeout: 10_000,
});
// uses the 10s default timeout
const [r1] = await routes.users.sayHello({id: '1', name: 'John', surname: 'Doe'}).call();
// overrides to 2s for this specific call
const [r2, err] = await routes.users.sayHello({id: '1', name: 'John', surname: 'Doe'}).call({timeout: 2000});
Use client.abort() to cancel all in-flight requests at once -- useful for SPA page navigation. New requests work normally after calling abort().
import {initClient} from '@mionjs/client';
import type {MyApi} from './server.routes.ts';
const {client, routes} = initClient<MyApi>({baseURL: 'http://localhost:3000'});
// start multiple requests
const p1 = routes.users.sayHello({id: '1', name: 'John', surname: 'Doe'}).call();
const p2 = routes.utils.sum(5, 2).call();
// cancel ALL in-flight requests (e.g. user navigated away)
client.abort();
// both return 'request-aborted' errors
const [, err1] = await p1; // err1.type === 'request-aborted'
const [, err2] = await p2; // err2.type === 'request-aborted'
// new requests work normally after abort
const [greeting] = await routes.users.sayHello({id: '1', name: 'John', surname: 'Doe'}).call();
client.destroy() also aborts all in-flight requests before clearing handlers.Cancellation and timeouts work seamlessly with middleFns and routesFlow. Pass signal and timeout alongside other setup options.
import {initClient, routesFlow} from '@mionjs/client';
import type {MyApi} from './server.routes.ts';
import {HeadersSubset} from '@mionjs/core';
const {routes, middleFns} = initClient<MyApi>({baseURL: 'http://localhost:3000'});
const controller = new AbortController();
// cancellation works with middleFns
const [result, error, mfResults, mfErrors] = await routes.users.sayHello({id: '1', name: 'John', surname: 'Doe'}).call({
middleFns: {auth: middleFns.auth(new HeadersSubset({Authorization: 'myToken-XYZ'}))},
timeout: 5000,
signal: controller.signal,
});
// and with routesFlow
const [[sum, greeting], [sumError, greetingError]] = await routesFlow([
routes.utils.sum(5, 2),
routes.users.sayHello({id: '1', name: 'John', surname: 'Doe'}),
]).call({
middleFns: {auth: middleFns.auth(new HeadersSubset({Authorization: 'myToken-XYZ'}))},
timeout: 10_000,
});
routesFlow with a timeout, the timeout applies to the entire batch request, not to individual routes within it.Aborting an in-flight HTTP request stops the client from waiting — it does not undo work on the server. By the time the abort signal fires, the server may have:
mion's client.abort(), per-request signal, and timeout options are best-effort: they cleanly cancel the client-side wait, but the server-side outcome is the same as if the connection had simply dropped. Server-side cancellation propagation is a separate concern not currently implemented.
request-aborted differently in your UI — don't show an error toast for user-initiated cancels; ignore them silently or show a neutral indicator.Cancellation is unconditionally safe for queries (GET routes defined with query()): the worst case is wasted work on the server. For mutations (mutation() and default route() calls), apply the patterns above and design with the assumption that any aborted request might have committed.