Skip to content

refactor: wrap multi-step DB operations in transactional RPCs #415

Description

@coderabbitai

Summary

Several flows introduced in #414 perform multiple sequential Supabase mutations without a wrapping transaction. A failure at any intermediate step leaves the database in an inconsistent state, and a retry can produce duplicate records.

The following operations should each be consolidated into a single Supabase RPC (PostgreSQL function with BEGIN … COMMIT) or a compensating rollback strategy.


1. Occurrence create flow — OccurrenceCreateFormContainer.tsx

Current sequential chain:

  1. addOccurrence — inserts the occurrence (committed immediately)
  2. addNote — inserts an optional note
  3. saveMetricValues — inserts metric value rows
  4. createOccurrenceStockUsages — inserts stock usage rows
  5. updateOccurrence — patches cost/currency back onto the already-committed occurrence
  6. updateStock (×N) — marks stocks as depleted

Risk: If step 4–6 fail, the occurrence exists without stock usages, with a null cost/currency, and stocks are not marked depleted. A retry duplicates the occurrence.

Proposed fix: A single create_occurrence RPC that atomically handles all inserts and the cost/currency derivation inside one transaction.


2. Occurrence update flow — OccurrenceUpdateFormContainer.tsx

Current sequential chain:

  1. deleteOccurrenceStockUsages — removes removed usages (triggers restore remaining_items)
  2. updateOccurrenceStockUsage (×N, Promise.all) — updates changed quantities
  3. createOccurrenceStockUsages — inserts newly added usages
  4. updateStock (×N) — marks stocks as depleted
  5. updateOccurrence — patches the occurrence itself (cost, currency, etc.)
  6. addNote / updateNote / deleteNote — note management
  7. saveMetricValues — metric values
  8. refreshHabitStocks — store refresh

Risk: Partial failure between any of steps 1–5 leaves stock remaining-item counts and occurrence cost/currency in an inconsistent state.

Proposed fix: A single update_occurrence RPC that wraps stock usage mutations and the occurrence patch in one transaction, with the store refresh happening client-side only after success.


3. Stock creation — stocks.store.tsaddStock

Current sequential chain:

  1. createStock — inserts the habit_stocks row
  2. createStockMetricDefaults — inserts metric default rows for the new stock

Risk: If step 2 fails, the stock exists with no metric defaults, silently breaking metric compound behaviour for that stock.

Proposed fix: A create_stock_with_defaults RPC that inserts both the stock and its metric defaults atomically.


4. Stock metric-defaults update — stocks.store.tsupdateStockMetricDefaults

Current sequential chain:

  1. destroyStockMetricDefaults — deletes all existing defaults for the stock
  2. createStockMetricDefaults — inserts the new set of defaults

Risk: If step 2 fails after step 1 completes, the stock is left with no metric defaults at all.

Proposed fix: A replace_stock_metric_defaults RPC (or use an upsert + targeted delete inside a transaction) so the delete and re-insert are atomic.


References

Metadata

Metadata

Assignees

Labels

No labels
No labels

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions