Skip to Content
DocsHow to useErrors and recovery

Errors and recovery

Tiaude is strict by design.

When it rejects an operation, it is usually preventing you from persisting a misleading score.

The recovery strategy is simple:

if incremental state cannot be trusted, rebuild from raw events with scoreUser()

Exported errors

Tiaude exports:

ConfigValidationError InvalidEventError ConfigMismatchError OutOfOrderEventError StateValidationError StateUserMismatchError

ConfigValidationError

The scorer config is invalid.

Common causes:

  • invalid signal type;
  • duplicate signal IDs;
  • missing threshold on a frequency signal;
  • threshold on a non-frequency signal;
  • invalid score levels;
  • invalid half-life;
  • invalid action type.

Recovery:

fix the config before starting the app

This is usually a deployment-time error, not a per-user runtime error.

InvalidEventError

An event or date input is invalid.

Common causes:

  • missing event object;
  • empty event name;
  • invalid timestamp;
  • ambiguous date string;
  • impossible calendar date;
  • now earlier than the event timestamp;
  • now earlier than the latest event in scoreUser();
  • time travel on an already computed state.

Recovery:

reject or fix the bad input

For timestamps, prefer:

new Date().toISOString()

ConfigMismatchError

The saved state was produced with an incompatible config.

Common causes:

  • signal added or removed;
  • signal weight changed;
  • threshold changed;
  • half-life changed;
  • event name changed;
  • score level settings changed.

Recovery:

load raw events -> scoreUser() -> persist new state

OutOfOrderEventError

track() received an event older than the saved state.

This can happen with queues, webhooks, retries, or delayed event delivery.

Recovery:

load raw events -> scoreUser() -> persist new state

scoreUser() sorts events before scoring, so it is the correct recovery path.

StateValidationError

The supplied previousState is malformed or internally inconsistent.

Common causes:

  • missing fields;
  • wrong persisted JSON shape;
  • corrupted counters;
  • impossible timestamps;
  • missing signal state;
  • invalid residue shape.

Recovery:

discard the unsafe state load raw events scoreUser() persist the rebuilt state

StateUserMismatchError

The supplied state belongs to another user.

This usually indicates a persistence or lookup bug.

Recovery:

do not reuse that state load the correct state or rebuild for the current user

Do not silently recover from this unless you are sure your data access layer is correct.

import { ConfigMismatchError, OutOfOrderEventError, StateValidationError, } from "tiaude"; export function shouldRecompute(error: unknown) { return ( error instanceof ConfigMismatchError || error instanceof OutOfOrderEventError || error instanceof StateValidationError ); }
try { const result = scorer.track({ userId, previousState: user.churnState ?? null, event, }); await saveChurnState(userId, result.state); return result; } catch (error) { if (!shouldRecompute(error)) { throw error; } const events = await loadUserEvents(userId); const result = scorer.scoreUser({ userId, events, }); await saveChurnState(userId, result.state); return result; }
try { const result = scorer.refreshScore({ userId, previousState: user.churnState, }); await saveChurnState(userId, result.state); return result; } catch (error) { if (!shouldRecompute(error)) { throw error; } const events = await loadUserEvents(userId); const result = scorer.scoreUser({ userId, events, }); await saveChurnState(userId, result.state); return result; }

What not to do

Do not repair Tiaude state manually.

Do not create missing signal states yourself.

Do not ignore ConfigMismatchError.

Do not continue after OutOfOrderEventError with the same previous state.

Use scoreUser() from raw events instead.

Final rule

track() and refreshScore() are optimized continuation paths. scoreUser() is the safe rebuild path. raw events are the source of truth.