(params: {
userId: string
amount: number
type: GrantType
description: string
expiresAt: Date | null
operationId: string
tx?: DbTransaction
logger: Logger
})
| 256 | * When called without a transaction, acquires the advisory lock automatically. |
| 257 | */ |
| 258 | export async function grantCreditOperation(params: { |
| 259 | userId: string |
| 260 | amount: number |
| 261 | type: GrantType |
| 262 | description: string |
| 263 | expiresAt: Date | null |
| 264 | operationId: string |
| 265 | tx?: DbTransaction |
| 266 | logger: Logger |
| 267 | }) { |
| 268 | const { userId, tx, logger } = params |
| 269 | |
| 270 | // If a transaction is provided, the caller is responsible for locking |
| 271 | // (e.g., triggerMonthlyResetAndGrant which does multiple grants in one tx) |
| 272 | if (tx) { |
| 273 | await executeGrantCreditOperation({ ...params, tx }) |
| 274 | return |
| 275 | } |
| 276 | |
| 277 | // Otherwise, wrap in advisory lock to serialize with other credit operations for this user |
| 278 | await withAdvisoryLockTransaction({ |
| 279 | callback: async (tx) => { |
| 280 | await executeGrantCreditOperation({ ...params, tx }) |
| 281 | }, |
| 282 | lockKey: `user:${userId}`, |
| 283 | context: { userId, operationId: params.operationId, type: params.type }, |
| 284 | logger, |
| 285 | }).then(({ result }) => result) |
| 286 | } |
| 287 | |
| 288 | /** |
| 289 | * Processes a credit grant request with retries and failure logging. |
no test coverage detected