The failure vocabulary is core, and plain Hem already provides structured error handling. error declares a tagged error class:
error QuotaExceeded({ orgId: string })class QuotaExceeded extends Error {
readonly _tag = "QuotaExceeded";
readonly orgId: string;
constructor(fields: { readonly orgId: string }) {
super("QuotaExceeded");
this.name = "QuotaExceeded";
this.orgId = fields.orgId;
}
}A declared cause or message field forwards to super; export error exports the class. A statement that only drops a value is refused (HEM2018): a bare push(tx, doc) that returns a Promise and is never awaited, or a bare comparison, is almost always a mistake: await it, use the value, or discard it on purpose with let _ = push(tx, doc). That discard emits void (push(tx, doc));, so strict TypeScript and no-floating-Promise rules see the intent without an unused local.
raise throws one (inserting new for locally declared error types), and postfix catch handles: tag arms test with instanceof, a binding arm e => … catches everything, and (the rule that makes it composable) anything unmatched propagates. These are JavaScript exceptions: the error values are typed, but a raise leaves no trace in the function’s type, so callers are not made to handle it. When a failure belongs in the signature, return a Result<T, E> (§19) or move to Effect (§17):
error SaveFailed({ doc: string })
let save = async (doc: Doc) => {
use tx = db.begin() // released at block exit — even on throw
await push(tx, doc) catch {
| QuotaExceeded(_) => cached(doc)
| _ => raise SaveFailed({ doc: doc.id })
}
}class SaveFailed extends Error {
readonly _tag = "SaveFailed";
readonly doc: string;
constructor(fields: { readonly doc: string }) {
super("SaveFailed");
this.name = "SaveFailed";
this.doc = fields.doc;
}
}
const save = async (doc: Doc) => {
using tx = db.begin(); // released at block exit — even on throw
let _hemValue;
try {
_hemValue = await push(tx, doc);
} catch (error) {
if (error instanceof QuotaExceeded) {
_hemValue = cached(doc);
} else {
throw new SaveFailed({ doc: doc.id });
}
}
return _hemValue;
};(An AsyncDisposable transaction would lower to await using; the checker decides per resource.)
use binds a resource: in plain code that means Disposable/AsyncDisposable, lowered to the platform’s using/await using, which Workers RPC stubs and Durable Object handles speak natively. try { } still exists for multi-statement protected regions, but postfix catch is the idiomatic form; it attaches to any expression, await included. In Part II these same keywords change transport (errors travel down a typed channel instead of up the stack, so the types start recording them, and releases survive interruption) without changing a line of what you just learned.