RPC client specs¶
Types¶
Client-side types will be manually maintained so as to follow the behavior of the backend.
(Sean did a modicum of research into whether we’d be able to auto-generate TypeScript types from the backend code, but this didn’t seem viable.)
Examples of how the API would be called¶
Non-batched¶
/** @type {ApiRequest<Spacecraft>>} */
const request = api.spacecrafts.get({ id: 42 });
/** @type {CancellablePromise<Spacecraft>} */
const promise = request.run();
/** @type {Spacecraft} */
const spacecraft = await promise; // Might throw!
Batched¶
/** @type {[ApiResult<Spacecraft>, ApiResult<Voyage>]} */
const [spacecraftResult, voyageResult] = await batchSend(
api.spacecrafts.get({ id: 42 }),
api.voyages.get({ id: 100 }),
);
/** @type {Spacecraft} */
const spacecraft = spacecraftResult.unwrap(); // Might throw!
/** @type {Voyage} */
const voyage = voyageResult.unwrap(); // Might throw!
Lower-level types¶
(Some of these would actually be classes, but the type defs written below give you a rough idea of how they would work.)
ApiResult¶
interface ApiOk<T> {
status: 'ok';
value: T;
}
interface ApiError {
status: 'error';
code: number;
message: string;
data?: unknown;
}
type ApiResult<T> = ApiOk<T> | ApiError;
ApiRequest¶
class ApiRequest<T> {
/**
* The simplest, most common way to run an API request.
*
* @throws `ApiError` when awaited if any errors are encountered.
*/
run(): CancellablePromise<T> {
throw Error('Not yet implemented');
}
/**
* Provides more fine-grained control instead of `run` by returning
* ApiResult which can be type-narrowed manually.
*
* Will not reject or throw errors.
*/
send(): CancellablePromise<ApiResult<T>> {
throw Error('Not yet implemented');
}
}
Error handling¶
Error handling with try/catch¶
try {
const spacecraft = await api.spacecrafts.get({ id: 42 }).run();
toast.success(spacecraft.name);
} catch (e) {
toast.error(getErrorMessage(e));
}
Error handling with match
¶
match(await api.spacecrafts.get({ id: 42 }).send(), 'status', {
ok: ({ value: spacecraft }) => toast.success(spacecraft.name),
error: ({ message }) => toast.error(message),
});
(I already implemented this match
function in #3037 here.)