This explains why the SDK never throws, what a Result is, and how to read a LiganticError.
A fallible SDK operation returns a value that is the outcome, rather than signaling failure by throwing.
The reasons:
404 is an expected, handleable outcome — not an exceptional one. Returning it lets you branch on it cleanly without try/catch.Promise<LiganticResult<T>>, so the handling code is uniform across the whole SDK.try/catch scope where a failure can be silently ignored. You must inspect .ok.error: ... output is the Err side of the same result, rendered for a human.Result Typetype Result<T, E> = { ok: true; value: T } | { ok: false; error: E };
type LiganticResult<T> = Result<T, LiganticError>;Every operation returns Promise<LiganticResult<T>>. You check the ok flag:
const schema = await client.schemas.get(spaceId, schemaId);
if (schema.ok) {
// schema.value is typed as Schema
} else {
// schema.error is a LiganticError
}Helpers are exported for convenience:
ok(value) / err(error) — construct each side.tryOr(fn, toError) — run a function and capture any thrown error as the Err side. This is the boundary where an unexpected exception (for example an aborted fetch) becomes a LiganticError.LiganticErrorLiganticError mirrors the API's documented error envelope { message, code, issues? } and adds transport context:
Field | Meaning |
|---|---|
| Human-readable description. |
| HTTP status, or |
| The API error code, for example, |
| Validation issues, present for |
| The |
|
|
|
|
A few examples:
// API returned 404.
error.status; // 404
error.code; // "NOT_FOUND"
error.message; // "..."
// The request never reached the server.
error.status; // null
error.code; // "NETWORK_ERROR"
error.isTransportError; // trueThe retry policy is idempotent-only and keyed off isRetryable:
GET, DELETE, and PUT are auto-retried.408/429/5xx.POST and PATCH are never auto-retried — a non-idempotent failure is surfaced immediately as an Err result so you can decide what to do.So by the time you inspect a result, any retryable idempotent failure has already been retried up to the configured maxRetries.
const result = await client.flows.run(spaceId, flowId, { inputs });
if (!result.ok) {
const e = result.error;
// Log or surface. Include e.requestId for support.
throw new Error(`${e.code}: ${e.message}${e.requestId ? ` (request ${e.requestId})` : ""}`);
}
const runId = result.value.flowExecutionId;For the CLI, you don't write this at all — the CLI renders the Err side as error: <message> on stderr and exits non-zero.