* Core grant operation that performs the actual credit grant logic. * This should be called within a transaction that holds the appropriate advisory lock. * Uses ON CONFLICT DO NOTHING for idempotency - duplicate grants are silently ignored.
(params: {
userId: string
amount: number
type: GrantType
description: string
expiresAt: Date | null
operationId: string
tx: DbTransaction
logger: Logger
})
| 115 | * Uses ON CONFLICT DO NOTHING for idempotency - duplicate grants are silently ignored. |
| 116 | */ |
| 117 | async function executeGrantCreditOperation(params: { |
| 118 | userId: string |
| 119 | amount: number |
| 120 | type: GrantType |
| 121 | description: string |
| 122 | expiresAt: Date | null |
| 123 | operationId: string |
| 124 | tx: DbTransaction |
| 125 | logger: Logger |
| 126 | }) { |
| 127 | const { |
| 128 | userId, |
| 129 | amount, |
| 130 | type, |
| 131 | description, |
| 132 | expiresAt, |
| 133 | operationId, |
| 134 | tx, |
| 135 | logger, |
| 136 | } = params |
| 137 | |
| 138 | const now = new Date() |
| 139 | |
| 140 | // First check for any negative balances. |
| 141 | // This is the ONLY place debt is cleared. The consume path |
| 142 | // (consumeFromOrderedGrants in balance-calculator.ts) only deepens |
| 143 | // debt on overflow; it never repays it. New credit grants zero out |
| 144 | // existing debt rows here and subtract the total debt from the |
| 145 | // granted amount. |
| 146 | const negativeGrants = await tx |
| 147 | .select() |
| 148 | .from(schema.creditLedger) |
| 149 | .where( |
| 150 | and( |
| 151 | eq(schema.creditLedger.user_id, userId), |
| 152 | or( |
| 153 | isNull(schema.creditLedger.expires_at), |
| 154 | gt(schema.creditLedger.expires_at, now), |
| 155 | ), |
| 156 | ), |
| 157 | ) |
| 158 | .then((grants) => grants.filter((g) => g.balance < 0)) |
| 159 | |
| 160 | let inserted = false |
| 161 | let fullyConsumedByDebt = false |
| 162 | |
| 163 | if (negativeGrants.length > 0) { |
| 164 | const totalDebt = negativeGrants.reduce( |
| 165 | (sum, g) => sum + Math.abs(g.balance), |
| 166 | 0, |
| 167 | ) |
| 168 | for (const grant of negativeGrants) { |
| 169 | await tx |
| 170 | .update(schema.creditLedger) |
| 171 | .set({ balance: 0 }) |
| 172 | .where(eq(schema.creditLedger.operation_id, grant.operation_id)) |
| 173 | } |
| 174 | const remainingAmount = Math.max(0, amount - totalDebt) |
no test coverage detected