Skip to content

Support Recoverable Validation Flow for Explicit Transactions #22

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
KyleAMathews opened this issue Apr 28, 2025 · 1 comment
Open

Comments

@KyleAMathews
Copy link
Collaborator

KyleAMathews commented Apr 28, 2025

Problem

Currently, transaction failures are terminal: if a transaction fails during persistence, it is marked as failed and rolled back immediately.
However, validation failures (e.g., missing fields, business logic rejections) are recoverable and should allow the user or application to correct inputs and retry without discarding the original transaction.

Without this distinction, user workflows involving iterative correction and submission become awkward and error-prone.

Proposed Solution

Extend the transaction state machine to explicitly differentiate between fatal and recoverable failures.

Updated Transaction States

  • pending: Transaction created but not yet persisted.
  • persisting: Transaction being sent to backend.
  • completed: Transaction successfully persisted.
  • failed_fatal: Non-recoverable error (e.g., network failure, unexpected server error). Transaction rolled back immediately.
  • failed_recoverable: Validation or business rule failure. Transaction paused, validation errors stored, eligible for manual retry.

Validation Error Storage

Validation errors are attached to the transaction metadata.

Shape:

interface ValidationError {
  field: string;
  message: string;
}

Errors are replaced on each persist attempt.
Errors are read-only by default.

Developer API Additions

Transactions returned by db.transaction() will expose:

interface Transaction {
  retry(): void;
  abort(): void;
  validationErrors: ValidationError[];
}
  • retry() reattempts persistence using the corrected local state.
  • abort() discards the transaction and rolls back optimistic changes.

Persist Function Responsibility

Developers may implement local validation inside persist() before sending network requests.
Developers can reject immediately inside persist() if validation fails, setting validation errors without network activity.

Example:

persist: (transaction) => {
  if (!transaction.data.email.includes('@')) {
    transaction.setValidationErrors([{ field: 'email', message: 'Invalid email address.' }]);
    return;
  }
  return api.save(transaction.data);
}

Design Principles

  • Recovery and correction are opt-in, not automatic.
  • No retries are performed without explicit developer/user intent.
  • Validation handling introduces no new mandatory complexity for implicit or one-off mutations.
  • Only explicit transactions support validation recovery; implicit per-collection mutations remain terminal on failure.

Future Work

  • Expose optional preflight validation utilities for common patterns.
  • Potentially surface UI primitives (e.g., useTransaction(transactionId)) to bind error states cleanly into forms.
@KyleAMathews
Copy link
Collaborator Author

We won't get this done before the initial release but switching the fatal state to failed_fatal will make it easy to add later.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant