Safe vs unsafe functions
When you import from @passlock/client or @passlock/server you’re pulling in unsafe functions i.e. functions that could throw. You’ll need to catch any errors, then use one of our type guards to narrow down the error:
import { registerPasskey, isDuplicatePasskeyError } from "@passlock/client";
try { // result is the successful outcome const result = await registerPasskey({ ... }) console.log("Registration success");} catch (e) { if (isDuplicatePasskeyError(e)) { console.error("Duplicate passkey"); }}It’s easy to miss potential errors as they’re not reflected in the type system, only the JSDoc. We therefore expose (and recommend you use) safe variants, which return Promise<Result<T, E>> for expected success and error outcomes.
Safe variants are not a guarantee that nothing will ever throw. They wrap the expected Passlock success and error payloads, but unexpected runtime defects can still surface as exceptions.
When result.success is true, use result.value. When it’s false, use result.error to narrow the type:
import { registerPasskey, isDuplicatePasskeyError} from "@passlock/client/safe";
// result is a Result<RegistrationSuccess, RegistrationError>const result = await registerPasskey({ ... })
if (result.success) { console.log(result.value.code);} else if (isDuplicatePasskeyError(result.error)) { console.error("Duplicate passkey");} else { console.error(result.error.message);}The wrapped payload still preserves its original shape, so existing _tag checks and isX(...) type guards continue to work. For new code, prefer branching on result.success.