Summary
The wasm bindings reject with a plain string — the error's Display text — so JavaScript consumers cannot branch on error kind without substring-matching. They should reject with a structured error carrying a stable kind (and entity/id where applicable).
Current behavior
stitch-wasm/src/lib.rs:
fn err<E: std::fmt::Display>(e: E) -> JsValue {
JsValue::from_str(&e.to_string())
}
Every fallible binding maps its Result error through err, so a rejected promise carries a string primitive, e.g.:
Error::Conflict (409) → "conflict for <entity>/<id>"
Error::Ownership (403) → "ownership denied for <entity>/<id>"
Error::NotFound (404) → "entity not found: <entity>/<id>"
There is no code, kind, or structure. Consumers are forced into fragile matching such as String(err).startsWith("conflict for") or err.message.includes("conflict"), which breaks silently if the Display wording ever changes.
Motivation
The Store::create conflict-rollback fix (stitch-sync 0.5.0) makes create reject on a unique-key loss so callers can implement exclusive claims (e.g. seat holds). But to detect the loss, the caller must string-match the Display text. A structured error lets them write if (err.kind === "conflict") and drop the substring heuristic entirely.
Proposal
Reject with a JS object (or a wasm-bindgen error type) exposing at least:
kind: a stable discriminant — "conflict" | "ownership" | "notFound" | "timeout" | "connectionClosed" | "sessionInvalid" | "config" | "serde" | "io" | "mqtt" | "mqdb" (mirroring the Error variants / classifier methods is_conflict / is_ownership / …).
entity, id where the variant carries them.
message: the existing Display string, for logging.
Keep it backward-tolerant if feasible (e.g. the object also stringifies to the current text), or treat it as a breaking change in a minor bump and document the migration.
Notes
Summary
The wasm bindings reject with a plain string — the error's
Displaytext — so JavaScript consumers cannot branch on error kind without substring-matching. They should reject with a structured error carrying a stablekind(andentity/idwhere applicable).Current behavior
stitch-wasm/src/lib.rs:Every fallible binding maps its
Resulterror througherr, so a rejected promise carries a string primitive, e.g.:Error::Conflict(409) →"conflict for <entity>/<id>"Error::Ownership(403) →"ownership denied for <entity>/<id>"Error::NotFound(404) →"entity not found: <entity>/<id>"There is no
code,kind, or structure. Consumers are forced into fragile matching such asString(err).startsWith("conflict for")orerr.message.includes("conflict"), which breaks silently if theDisplaywording ever changes.Motivation
The
Store::createconflict-rollback fix (stitch-sync 0.5.0) makescreatereject on a unique-key loss so callers can implement exclusive claims (e.g. seat holds). But to detect the loss, the caller must string-match the Display text. A structured error lets them writeif (err.kind === "conflict")and drop the substring heuristic entirely.Proposal
Reject with a JS object (or a
wasm-bindgenerror type) exposing at least:kind: a stable discriminant —"conflict" | "ownership" | "notFound" | "timeout" | "connectionClosed" | "sessionInvalid" | "config" | "serde" | "io" | "mqtt" | "mqdb"(mirroring theErrorvariants / classifier methodsis_conflict/is_ownership/ …).entity,idwhere the variant carries them.message: the existingDisplaystring, for logging.Keep it backward-tolerant if feasible (e.g. the object also stringifies to the current text), or treat it as a breaking change in a minor bump and document the migration.
Notes
Erroralready exposes the classifiers (is_conflict,is_ownership,is_transient, …) — this is purely about preserving that discriminant across the wasm boundary instead of flattening toDisplay.