Skip to content

fix(db-part-2): close optional-executor contract traps#4989

Merged
icecrasher321 merged 1 commit into
stagingfrom
improvement/db-pattern-2
Jun 12, 2026
Merged

fix(db-part-2): close optional-executor contract traps#4989
icecrasher321 merged 1 commit into
stagingfrom
improvement/db-pattern-2

Conversation

@icecrasher321

Copy link
Copy Markdown
Collaborator

Summary

Add DbClient (primary | replica, excludes tx handles) and switch the five billing read functions off DbOrTx — a transaction can no longer be passed into multi-query read fans that would partially escape it

Type of Change

  • Bug fix

Testing

Tested manually

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

@vercel

vercel Bot commented Jun 12, 2026

Copy link
Copy Markdown

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

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Skipped Skipped Jun 12, 2026 2:18am

Request Review

@cursor

cursor Bot commented Jun 12, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Touches billing read aggregation and deployment side effects (MCP notify timing, schedule transactions); wrong executor or early notify could skew dashboards or briefly expose rolled-back MCP state.

Overview
Introduces DbClient as a read-routing type (primary or replica, not transaction handles) and retargets billing summary/usage/credit/daily-refresh read helpers from DbOrTx to DbClient, threading an optional executor through subscription lookups, org aggregation, ledgers, and limits so display paths can use dbReplica while enforcement/webhooks stay on the primary.

getUserUsageData still inserts/reads userStats on the primary after ensureUserStatsExists so replica lag cannot miss a just-created row.

MCP workflow sync tightens the contract: in-transaction callers never publish server notifications; standalone paths notify after commit. createSchedulesForDeploy takes an optional tx (join caller transaction or open one) instead of a required DB context, with a test for write-through behavior.

Reviewed by Cursor Bugbot for commit eaa0bc3. Configure here.

@greptile-apps

greptile-apps Bot commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR tightens the database executor contract across billing read-path helpers and the MCP sync layer. It introduces a DbClient type (primary or replica, no transaction handles) to replace DbOrTx on functions that fan out multiple independent SELECTs — preventing callers from accidentally scoping those reads inside a transaction that holds a pooled connection.

  • Five billing read functions (getSimplifiedBillingSummary, getOrganizationBillingData, getUserUsageData, getEffectiveCurrentPeriodCost, getCreditBalance) and their transitive sub-calls are switched to DbClient, with ensureUserStatsExists and its immediate userStats read intentionally kept on the primary for read-your-write safety.
  • syncMcpToolsForWorkflow and removeMcpToolsForWorkflow have their notify parameter removed and the post-commit notification semantics hardened through the discriminated union type: the tx arm forbids notify: true, the standalone arm notifies after db.transaction completes.
  • createSchedulesForDeploy makes tx optional, drops the hasScheduleWriteMethods duck-type guard in favour of an identity check (tx !== db), and adds a test case covering the pass-through path.

Confidence Score: 4/5

Safe to merge — all changed call-sites correctly thread the executor, post-commit notification is properly enforced by the discriminated union, and write paths continue to target the primary.

The executor threading is thorough and consistent across the billing fan-outs, and the MCP notification refactor is logically sound. The one open question is whether TypeScript's structural checker actually rejects a PgTransaction where DbClient is expected — if it doesn't, the new type alias is a documentation aid rather than a hard guard, and a caller could still route multi-step reads inside a transaction without a compile error.

apps/sim/lib/db/types.ts — the DbClient type alias; worth verifying whether Drizzle's structural types make the transaction-handle exclusion compiler-enforced or documentation-only.

Important Files Changed

Filename Overview
apps/sim/lib/db/types.ts Adds DbClient = typeof db — a named type alias for the primary or replica client that deliberately excludes transaction handles. The enforcement is documentation-level; Drizzle's structural typing means PgTransaction may remain assignable to typeof db.
apps/sim/lib/billing/core/billing.ts Switches getSimplifiedBillingSummary, aggregateOrgMemberStats, and getOrganizationSubscription from DbOrTx to DbClient. Executor is consistently threaded through all sub-calls. Enforcement-path functions (calculateOrgOverage, calculateSubscriptionOverage) are correctly left off the executor pattern and always use primary.
apps/sim/lib/billing/core/usage.ts Carefully mixes executor routing: ensureUserStatsExists (write) and the subsequent userStats SELECT in getUserUsageData stay on primary for read-your-write consistency, while subscription and billing aggregate reads fan out to the supplied executor. All computeDailyRefreshConsumed call-sites pass executor correctly.
apps/sim/lib/mcp/workflow-mcp-sync.ts Removes notify from SyncOptionsBase and re-expresses notification semantics through the discriminated union: the tx arm locks notify?: false, the standalone arm allows notify?: boolean. The standalone path correctly reads options.notify ?? true after the transaction commits.
apps/sim/lib/workflows/schedules/deploy.ts Makes tx optional and removes the hasScheduleWriteMethods structural check in favor of an identity guard tx && tx !== db. The guard correctly wraps calls without a real transaction in db.transaction(...).
apps/sim/lib/billing/core/organization.ts Switches getOrgMemberLedgerByUser and getOrganizationBillingData to DbClient. All direct queries and sub-calls consistently pass executor.
apps/sim/lib/billing/credits/balance.ts Threads executor: DbClient through getCreditBalance and getCreditBalanceForEntity consistently.
apps/sim/lib/billing/credits/daily-refresh.ts Switches computeDailyRefreshConsumed and getOrgMemberRefreshBounds from DbOrTx to DbClient. Both functions perform only SELECTs and are safe to route to a replica.
apps/sim/lib/workflows/deployment-outbox.ts Updates removeMcpToolsForWorkflow call site to the new 4-arg signature (dropped notify). Post-commit notifyMcpToolServers call is already present and unaffected.
apps/sim/lib/workflows/schedules/deploy.test.ts Removes the {} as any dbCtx stand-ins from existing tests and adds a new test case verifying that a provided transaction is used directly without opening a nested db.transaction.

Sequence Diagram

sequenceDiagram
    participant Caller
    participant syncMcp as syncMcpToolsForWorkflow
    participant removeMcp as removeMcpToolsForWorkflow
    participant db as db.transaction
    participant notify as notifyMcpToolServers

    Note over Caller,notify: Standalone path (no tx provided)
    Caller->>syncMcp: "{ workflowId, notify?: boolean }"
    syncMcp->>db: open transaction
    db->>syncMcp: "recursive call { tx, notify: false }"
    alt no valid start block
        syncMcp->>removeMcp: "(workflowId, tx, throwOnError=true)"
        removeMcp-->>syncMcp: affected tools (no notify)
    else valid start block
        syncMcp->>syncMcp: update MCP tool schemas in tx
        syncMcp-->>db: affected serverIds
    end
    db-->>syncMcp: tools (committed)
    syncMcp->>notify: if options.notify ?? true
    syncMcp-->>Caller: affected tools

    Note over Caller,notify: Transaction path (tx provided)
    Caller->>syncMcp: "{ tx, state, notify?: false }"
    alt no valid start block
        syncMcp->>removeMcp: "(workflowId, tx, throwOnError=true)"
        removeMcp-->>syncMcp: tools (no notify)
    else valid start block
        syncMcp->>syncMcp: update schemas inside caller tx
    end
    syncMcp-->>Caller: affected tools
    Note over Caller: Caller calls notifyMcpToolServers after commit
Loading

Reviews (1): Last reviewed commit: "fix(db): close optional-executor contrac..." | Re-trigger Greptile

Comment thread apps/sim/lib/db/types.ts
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.

1 participant