Skip to content

Conversation

@ryota-murakami
Copy link
Contributor

@ryota-murakami ryota-murakami commented Feb 11, 2026

Summary

Closes #61

  • Add 5 PostgreSQL RPC functions for atomic multi-step operations (migration file)
  • Replace fragile manual rollback in moveToMaintenance and restoreToBoard with single supabase.rpc() calls
  • Replace Promise.all N individual updates in 3 batch functions with atomic batch RPC calls
  • All RPC functions use SECURITY INVOKER to respect existing RLS policies
  • Net result: -161 lines of fragile rollback code → +110 lines of transaction-safe code

RPC Functions Added

Function Replaces Atomicity
move_to_maintenance 3-step insert→update→delete + manual rollback Single transaction
restore_to_board 3-step insert→update→delete + manual rollback Single transaction
batch_update_statuslist_positions Promise.all N updates Single transaction
batch_update_repocard_orders Promise.all N updates Single transaction
batch_update_board_positions Promise.all N updates Single transaction

Key Design Decisions

  • Hybrid approach: Validation (auth, ownership, duplicates) stays in TypeScript — read-only, safe outside transaction. Only mutation steps go in RPC.
  • CHECK constraint handling: projectinfo_target_exclusive_check requires exactly one of repo_card_id/maintenance_id non-null. The single UPDATE statement in move/restore RPCs sets both columns atomically — PostgreSQL evaluates CHECK at statement boundary, so the final state passes.

Test plan

  • pnpm typecheck — clean
  • pnpm lint — 0 warnings
  • pnpm build — successful
  • pnpm test — 85 files, 1202 tests passed
  • pnpm e2e — 317 passed, 1 skipped (pre-existing), 0 failed
  • Run pnpm db:reset against local Supabase to verify migration applies
  • Manual test: move card to maintenance and restore back

Summary by CodeRabbit

  • Bug Fixes

    • Batch updates for card positioning and ordering are now atomic, preventing partial failures and ensuring data consistency.
  • Performance

    • Optimized database operations by reducing round trips; card maintenance transitions now use efficient server-side functions.

Replace fragile manual rollback patterns with atomic RPC functions.
All mutation steps now run in a single PostgreSQL transaction —
automatic rollback on any failure, zero chance of partial state.

- Add 5 RPC functions: move_to_maintenance, restore_to_board,
  batch_update_statuslist_positions, batch_update_repocard_orders,
  batch_update_board_positions
- All use SECURITY INVOKER to respect existing RLS policies
- Refactor repo-cards.ts: replace 3-step insert→update→delete +
  manual rollback with single RPC call
- Refactor board.ts: replace Promise.all N updates with atomic
  batch RPC calls
- Net reduction: -161 lines of fragile rollback code, +110 lines
  of robust transaction-safe code
@vercel
Copy link
Contributor

vercel bot commented Feb 11, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
gitbox Ready Ready Preview, Comment Feb 11, 2026 2:03pm

Request Review

@coderabbitai
Copy link

coderabbitai bot commented Feb 11, 2026

Warning

Rate limit exceeded

@ryota-murakami has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 18 minutes and 18 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📝 Walkthrough

Walkthrough

This PR replaces multi-step database operations with atomic PostgreSQL RPC functions. Three batch update operations and two complex card management operations (moveToMaintenance, restoreToBoard) transition from manual Promise.all patterns and multi-step SQL with rollback logic to server-side atomic transactions, eliminating consistency risks.

Changes

Cohort / File(s) Summary
RPC Client Actions
src/lib/actions/board.ts, src/lib/actions/repo-cards.ts
Replaced per-item parallel updates and multi-step SQL operations with atomic RPC calls. board.ts converts three batch operations to rpc() calls with JSON payloads and error capture. repo-cards.ts simplifies moveToMaintenance and restoreToBoard from manual insert/update/delete sequences to single RPC calls with Sentry error handling.
TypeScript Type Definitions
src/lib/supabase/types.ts
Added explicit type signatures for five new database functions: batch_update_board_positions, batch_update_repocard_orders, batch_update_statuslist_positions, move_to_maintenance, and restore_to_board.
PostgreSQL Migration
src/supabase/migrations/20260212000000_transaction_rpc_functions.sql
Implements five PL/pgSQL functions for atomic operations. move_to_maintenance and restore_to_board perform 3-step card lifecycle transitions in single transactions. Three batch functions iterate JSONB update arrays for grid/order/position changes with per-item loops.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Poem

🔄 From steps scattered, now atomically bound,
RPC functions wrap chaos in transactions sound,
No orphaned records left strewn about,
Just PostgreSQL promises—transactional clout! ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed Title accurately reflects the main change: moving multi-step DB operations into PostgreSQL transactions for atomicity.
Linked Issues check ✅ Passed All objectives from issue #61 are met: three RPC functions for atomic operations (move_to_maintenance, restore_to_board) and three batch update RPCs replace fragile manual rollback and Promise.all patterns with transaction-safe supabase.rpc() calls.
Out of Scope Changes check ✅ Passed All changes directly support the PR objective of wrapping multi-step DB operations in transactions; no unrelated modifications detected.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/transaction-rpc-functions-61

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@morph-subagents
Copy link

🤖 Morph Preview Test

Looks like you hit your rate limits!

Please upgrade your limits here, or wait a few minutes and try again.

If you need help, reach out to support@morphllm.com.


Automated testing by Morph

@codecov-commenter
Copy link

codecov-commenter commented Feb 11, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 74.33%. Comparing base (aae7cc9) to head (c681f5f).
⚠️ Report is 9 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##             main      #90   +/-   ##
=======================================
  Coverage   74.33%   74.33%           
=======================================
  Files         119      119           
  Lines        3873     3873           
  Branches     1030     1062   +32     
=======================================
  Hits         2879     2879           
  Misses        971      971           
  Partials       23       23           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/lib/actions/board.ts (1)

569-570: ⚠️ Potential issue | 🟡 Minor

Stale JSDoc: "in parallel" should be "atomically".

Line 569 says "Updates multiple boards' position values in parallel" but the operation is now a single atomic RPC call, not parallel updates.

📝 Suggested doc fix
 /**
  * Batch update board positions for DnD reordering.
- * Updates multiple boards' position values in parallel.
+ * Updates multiple boards' position values atomically via a PostgreSQL RPC.
  *
src/lib/actions/repo-cards.ts (1)

272-294: ⚠️ Potential issue | 🟡 Minor

Orphaned JSDoc block — not attached to any function.

This JSDoc for moveToMaintenance is stranded above restoreToBoard's JSDoc+function (lines 296–419). The actual moveToMaintenance export is at line 521. This block documents nothing as-is.

Move it directly above line 521, or remove it since the function itself could carry its own doc.

🤖 Fix all issues with AI agents
In `@src/supabase/migrations/20260212_transaction_rpc_functions.sql`:
- Around line 153-158: batch_update_board_positions currently updates only
position for each board row but does not set updated_at like the other batch
functions; either add updated_at = now() to the UPDATE in
batch_update_board_positions (the UPDATE statement inside the FOR v_item IN
jsonb_array_elements(p_updates) loop) to match batch_update_statuslist_positions
and batch_update_repocard_orders, or if this omission is intentional add a brief
comment above the function explaining why position-only updates must not touch
updated_at so reviewers know it’s deliberate.

@github-actions
Copy link

github-actions bot commented Feb 11, 2026

🧪 E2E Coverage Report (Sharded: 12 parallel jobs)

Metric Coverage
Lines 93.59%
Functions 17.65%
Branches 16.84%
Statements 30.41%

📊 Full report available in workflow artifacts

- Add updated_at = now() to batch_update_board_positions for consistency
- Fix stale JSDoc: "in parallel" → "atomically via PostgreSQL RPC"
- Move orphaned moveToMaintenance JSDoc to actual function location
@morph-subagents
Copy link

🤖 Morph Preview Test

Looks like you hit your rate limits!

Please upgrade your limits here, or wait a few minutes and try again.

If you need help, reach out to support@morphllm.com.


Automated testing by Morph

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@src/supabase/migrations/20260212_transaction_rpc_functions.sql`:
- Around line 1-3: The migration filename uses an 8-digit date; rename
"20260212_transaction_rpc_functions.sql" to the 14-digit Supabase CLI format
like "20260212000000_transaction_rpc_functions.sql" (keep the
"transaction_rpc_functions" slug) and regenerate the migration via the Supabase
CLI (supabase migration new transaction_rpc_functions) or manually rename the
file to match YYYYMMDDHHMMSS_<description>.sql so it follows Supabase migration
ordering and tooling expectations; ensure any references to the old filename in
deployment scripts or changelogs are updated accordingly.
🧹 Nitpick comments (2)
src/lib/actions/repo-cards.ts (1)

566-586: Atomic move-to-maintenance via RPC looks solid.

Same pattern as restoreToBoard. One note: p_user_id is passed as a parameter to the RPC rather than using auth.uid() inside the SQL function. This is safe here because the value comes from the server-side supabase.auth.getUser() call (not client input), and RLS on the maintenance table should independently enforce ownership. Still, using auth.uid() directly inside the function would be more defense-in-depth.

♻️ Optional: use auth.uid() inside the SQL function

In the migration SQL (move_to_maintenance), replace the p_user_id parameter usage with auth.uid():

 CREATE OR REPLACE FUNCTION move_to_maintenance(
   p_card_id UUID,
-  p_user_id UUID,
   p_repo_owner TEXT,
   p_repo_name TEXT
 )
 ...
   INSERT INTO maintenance (user_id, repo_owner, repo_name)
-  VALUES (p_user_id, p_repo_owner, p_repo_name)
+  VALUES (auth.uid(), p_repo_owner, p_repo_name)
   RETURNING id INTO v_maint_id;

Then drop p_user_id from the TS call site as well.

src/supabase/migrations/20260212_transaction_rpc_functions.sql (1)

89-108: Batch update functions silently skip non-matching IDs.

All three batch functions (batch_update_statuslist_positions, batch_update_repocard_orders, batch_update_board_positions) iterate the JSONB array and run UPDATE statements, but if an ID doesn't match any row (e.g., stale UUID, RLS filters it out), the update silently succeeds with zero affected rows. The caller gets no indication that some items weren't updated.

This is probably fine for position reordering (the TS layer validates inputs), but worth documenting. If you want stronger guarantees, you could check FOUND after each UPDATE and raise an exception.

Also applies to: 116-135, 143-161

Rename 20260212_ → 20260212000000_ to follow Supabase CLI convention
@morph-subagents
Copy link

🤖 Morph Preview Test

Looks like you hit your rate limits!

Please upgrade your limits here, or wait a few minutes and try again.

If you need help, reach out to support@morphllm.com.


Automated testing by Morph

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In `@src/supabase/migrations/20260212000000_transaction_rpc_functions.sql`:
- Around line 35-36: After deleting the repocard row using DELETE FROM repocard
WHERE id = p_card_id, verify that the DELETE actually removed a row (e.g., check
FOUND or use GET DIAGNOSTICS row_count) and if zero rows were affected RAISE an
exception to abort the transaction so the previously inserted maintenance record
(inserted earlier in this RPC) is rolled back; reference the repocard table, the
p_card_id parameter and the earlier INSERT that creates the maintenance record
when adding the check and RAISE.
- Around line 76-77: The DELETE from maintenance using p_maintenance_id can be a
no-op and leave an orphaned maintenance row after the repocard insert; after
performing DELETE FROM maintenance WHERE id = p_maintenance_id, check that a row
was actually removed (e.g. DELETE ... RETURNING id INTO var OR use GET
DIAGNOSTICS / ROW_COUNT) and if no row was deleted, RAISE an exception to abort
and roll back the transaction so the repocard creation (the earlier insert that
mirrors move_to_maintenance behavior) cannot leave inconsistent data.
- Around line 9-17: Add a SET search_path clause to each SECURITY INVOKER RPC
function header in this file to ensure PostgREST resolves unqualified table
names consistently; specifically, modify the function signatures (e.g.,
move_to_maintenance and the other four RPC functions) to include SET search_path
= pg_catalog, public in the function header (i.e., alongside SECURITY INVOKER)
so the header becomes: ... RETURNS ... LANGUAGE plpgsql SECURITY INVOKER SET
search_path = pg_catalog, public ... before the function body.

- Add SET search_path = pg_catalog, public to all 5 RPC functions
- Add IF NOT FOUND guards after DELETE in move_to_maintenance
- Add IF NOT FOUND guards after DELETE in restore_to_board
@morph-subagents
Copy link

🤖 Morph Preview Test

Looks like you hit your rate limits!

Please upgrade your limits here, or wait a few minutes and try again.

If you need help, reach out to support@morphllm.com.


Automated testing by Morph

@ryota-murakami ryota-murakami merged commit c62046a into main Feb 11, 2026
20 checks passed
@ryota-murakami ryota-murakami deleted the fix/transaction-rpc-functions-61 branch February 11, 2026 14:14
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

Successfully merging this pull request may close these issues.

Wrap multi-step DB operations in database transactions

2 participants