Errors and Failures
This guide covers the practical failure rules in .sts, especially what to throw locally and what
to do when regular TS or JS throws something strange.
Local code throws Error
Inside .sts, local failure paths should throw Error values:
if (session === undefined) {
throw new Error("Missing session.");
}
This rule exists because arbitrary thrown values are hard to reason about and easy to mishandle.
When regular TS or JS throws something weird
Interop code can still throw anything. That is part of the honest JS platform model.
If that throw keeps propagating, soundscript normalizes it automatically.
Use normalizeThrown(...) only when you need a normalized error value inside the current function,
for example to log it, attach metadata, or return it in a value-level result:
// #[interop]
import { readCookieSession } from "../legacy/session.ts";
import { normalizeThrown } from "sts:failures";
export async function loadSession(userId: string): Promise<Result<Session, Error>> {
try {
const session = await readCookieSession(userId);
if (session === undefined) {
return { tag: "err", error: new Error("Missing session.") };
}
return { tag: "ok", value: session };
} catch (error) {
const normalized = normalizeThrown(error);
reportSessionFailure(normalized);
return { tag: "err", error: normalized };
}
}
That gives you an explicit error value to work with inside the function. It is different from rethrowing, which does not need manual normalization.
When to use thrown errors versus value-level failures
As a practical rule:
- throw
Errorfor local operational failures inside one module or request flow - use value-level failure types such as
Resultwhen the API should expose failure in the type - call
normalizeThrown(...)only when you need the normalized value in hand
Most teams can start with ordinary Error-based code and introduce richer failure values only where
the public API really benefits from them.
Why it matters
TypeScript often lets failure behavior disappear behind unchecked assumptions:
- a foreign function might throw a string
- a JSON parser result might be asserted into shape
- a promise rejection might be treated as if it were always an
Error
soundscript does not erase those problems. It asks the source to handle them directly.
Practical advice
Use boundary helpers where they buy clarity:
- request decoding
- JSON parsing
- interop-heavy infrastructure code
- queue and workflow layers
- persistence adapters
These are the places where “what can fail here?” should be obvious from the module body.