@nehorai/credits 2.1.0 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/adapters/index.cjs +2 -2
- package/dist/adapters/index.cjs.map +1 -1
- package/dist/adapters/index.d.cts +2 -2
- package/dist/adapters/index.d.ts +2 -2
- package/dist/adapters/index.js +2 -2
- package/dist/adapters/index.js.map +1 -1
- package/dist/core/index.cjs +102 -2
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +92 -5
- package/dist/core/index.d.ts +92 -5
- package/dist/core/index.js +92 -2
- package/dist/core/index.js.map +1 -1
- package/dist/index.cjs +272 -39
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6 -6
- package/dist/index.d.ts +6 -6
- package/dist/index.js +260 -39
- package/dist/index.js.map +1 -1
- package/dist/repository/index.cjs +247 -39
- package/dist/repository/index.cjs.map +1 -1
- package/dist/repository/index.d.cts +34 -6
- package/dist/repository/index.d.ts +34 -6
- package/dist/repository/index.js +244 -38
- package/dist/repository/index.js.map +1 -1
- package/dist/sdk/index.cjs.map +1 -1
- package/dist/sdk/index.d.cts +2 -2
- package/dist/sdk/index.d.ts +2 -2
- package/dist/sdk/index.js.map +1 -1
- package/dist/service/index.cjs +8 -4
- package/dist/service/index.cjs.map +1 -1
- package/dist/service/index.d.cts +2 -2
- package/dist/service/index.d.ts +2 -2
- package/dist/service/index.js +8 -4
- package/dist/service/index.js.map +1 -1
- package/dist/{types-BhCdep_t.d.ts → types-3c-IAwZI.d.ts} +38 -3
- package/dist/{types-Dzc443vy.d.cts → types-MaViK5tD.d.cts} +18 -0
- package/dist/{types-Dzc443vy.d.ts → types-MaViK5tD.d.ts} +18 -0
- package/dist/{types-wtksMa4n.d.cts → types-XSjNXO5l.d.cts} +38 -3
- package/package.json +70 -71
package/dist/index.d.cts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { S as SubscriptionTier, T as TierConfig, a as PortableReservation } from './types-
|
|
2
|
-
export { A as AIProviderType, B as BuiltinTier, g as CreditCheckResult, C as CreditOperationType, b as CreditSource, D as DeductCreditsResult, J as JournalReferenceType, M as MonthlyResetResult, f as PortableJournalEntry, P as PortableTransaction, d as PortableUsageLog, c as PortableUserCredits, R as ReservationStatus, h as ResourceType, g as SDKCreditCheckResult, e as SubscriptionExpiryResult, i as TransactionType, U as UsageHistoryEntry, j as UsageHistoryResponse, c as UserCredits, W as WithCreditsOptions, k as calculateAvailableCredits, t as toDate, l as toPortableTimestamp } from './types-
|
|
3
|
-
export { A as AddCreditsAtomicOptions, a as AddCreditsOutcome, b as AddCreditsV2Input, C as CommitOutcome, c as CreateJournalEntryInput, d as CreateReservationInput, e as CreateTransactionInput, f as CreateUsageLogInput, g as CreditBalanceUpdate, h as CreditRepositoryFactory, E as ExpireOutcome,
|
|
4
|
-
export { CREDIT_AMOUNT_MAX, CREDIT_AMOUNT_SCALE, CreditError, CreditErrorCode, CreditErrorCodeType, PaymentEventPayload, RESERVATION_STATUSES, RESERVED_JOURNAL_KEY_PREFIX, ReservationIntegrityView, StoredPaymentEvent, TERMINAL_RESERVATION_STATUSES, assertDirectStatusWriteAllowed, assertHoldPlaced, assertKnownReservationStatus, assertPublicJournalKey, assertRepresentableAmount, assertRepresentableFields, assertRepresentableTierAmount, assertUnkeyedDirectReservation, assertUnreferencedDirectTransaction, assertValidCreditAmount, assertValidIdempotencyKey, assertValidStoredAmount, assertValidStoredAmountRaw, backedBalanceFloor, classifyDatabaseError, commitReservationWithJournal, createIdempotencyConflictError, createInsufficientCreditsError, createInvalidAmountError, createInvalidOperationTypeError, createPaymentRefConflictError, createReservationAlreadyProcessedError, createReservationExpiredError, createReservationNotFoundError, createTransientError, createUnsupportedOperationError, createUserNotFoundError, describePaymentMismatch, getSqlState, hasPlacedHold, isCreditError, isIdempotencyConflictError, isInsufficientCreditsError, isRepresentableAmount, isReservationStatus, isReservedJournalKey, isTerminalReservationStatus, isTransientDatabaseError, isTransientError, isUniqueViolation, isValidCreditAmount, isValidIdempotencyKey, normalizePaymentRef, numericToCents, releaseReservationWithJournal, reserveCreditsForOperation, sameAmount, storedMonthlyLimit, sumAmounts, terminalStatusOf, toCents } from './core/index.cjs';
|
|
1
|
+
import { S as SubscriptionTier, T as TierConfig, a as PortableReservation } from './types-MaViK5tD.cjs';
|
|
2
|
+
export { A as AIProviderType, B as BuiltinTier, g as CreditCheckResult, C as CreditOperationType, b as CreditSource, D as DeductCreditsResult, J as JournalReferenceType, M as MonthlyResetResult, f as PortableJournalEntry, P as PortableTransaction, d as PortableUsageLog, c as PortableUserCredits, R as ReservationStatus, h as ResourceType, g as SDKCreditCheckResult, e as SubscriptionExpiryResult, i as TransactionType, U as UsageHistoryEntry, j as UsageHistoryResponse, c as UserCredits, W as WithCreditsOptions, k as calculateAvailableCredits, t as toDate, l as toPortableTimestamp } from './types-MaViK5tD.cjs';
|
|
3
|
+
export { A as AddCreditsAtomicOptions, a as AddCreditsOutcome, b as AddCreditsV2Input, C as CommitOutcome, c as CreateJournalEntryInput, d as CreateReservationInput, e as CreateTransactionInput, f as CreateUsageLogInput, g as CreditBalanceUpdate, h as CreditPolicyOptions, i as CreditRepositoryFactory, E as ExpireOutcome, j as ExpireReservationV2Options, I as ICreditRepository, k as ICreditRepositoryCreditsV2, l as ICreditRepositoryV2, J as JournalEntryQuery, R as ReleaseOutcome, m as ReservationTransitionOptions, n as ReserveCreditsV2Input, o as ReserveOutcome, T as TerminalReservationStatus, p as TierUpdateInput, U as UsageLogQuery, q as isCreditedOutcome, r as isReservedOutcome, s as isWinningOutcome, t as reservationJournalKey, u as supportsCreditsV2, v as supportsIdempotentCredit, w as toClientUserCredits } from './types-XSjNXO5l.cjs';
|
|
4
|
+
export { CREDIT_AMOUNT_MAX, CREDIT_AMOUNT_SCALE, CreditError, CreditErrorCode, CreditErrorCodeType, PaymentEventPayload, RESERVATION_STATUSES, RESERVED_JOURNAL_KEY_PREFIX, ReservationIntegrityView, StoredPaymentEvent, TERMINAL_RESERVATION_STATUSES, assertDirectStatusWriteAllowed, assertHoldPlaced, assertKnownReservationStatus, assertPublicJournalKey, assertRepresentableAmount, assertRepresentableFields, assertRepresentableTierAmount, assertUnkeyedDirectReservation, assertUnreferencedDirectTransaction, assertValidBalanceTarget, assertValidCreditAmount, assertValidIdempotencyKey, assertValidStoredAmount, assertValidStoredAmountRaw, assertValidTierBalanceInput, attributeExpiredBacking, backedBalanceFloor, cappedBalance, classifyDatabaseError, commitReservationWithJournal, createIdempotencyConflictError, createInsufficientCreditsError, createInvalidAmountError, createInvalidOperationTypeError, createPaymentRefConflictError, createReservationAlreadyProcessedError, createReservationExpiredError, createReservationNotFoundError, createTransientError, createUnsupportedOperationError, createUserNotFoundError, describePaymentMismatch, expiredHoldBackingPatch, getSqlState, hasPlacedHold, isCreditError, isIdempotencyConflictError, isInsufficientCreditsError, isRepresentableAmount, isReservationStatus, isReservedJournalKey, isTerminalReservationStatus, isTransientDatabaseError, isTransientError, isUniqueViolation, isValidCreditAmount, isValidIdempotencyKey, normalizePaymentRef, numericToCents, planBalanceCap, planBalanceTarget, planHoldCommit, releaseReservationWithJournal, reserveCreditsForOperation, sameAmount, settleExpiredHoldBacking, spendableBalance, storedMonthlyLimit, sumAmounts, terminalStatusOf, toCents } from './core/index.cjs';
|
|
5
5
|
export { D as DeferredExecutor, c as createDeferredExecutor, g as genericDeferred, n as noopDeferred, s as synchronousDeferred } from './deferred-C7rrRfkm.cjs';
|
|
6
6
|
import { z } from 'zod';
|
|
7
|
-
export { InMemoryCreditRepository, addCreditsThroughRepository, commitThroughRepository, createInMemoryCreditRepository, generateId, getNextMonthlyReset, releaseThroughRepository, reserveThroughRepository } from './repository/index.cjs';
|
|
7
|
+
export { BalanceValidationResult, InMemoryCreditRepository, addCreditsThroughRepository, assertValidBalanceUpdate, commitThroughRepository, createInMemoryCreditRepository, generateId, getNextMonthlyReset, releaseThroughRepository, reserveThroughRepository, validateBalanceUpdate } from './repository/index.cjs';
|
|
8
8
|
export { C as CreditsAuthProviderFactory, a as CreditsUser, I as ICreditsAuthProvider } from './types-OZU3I5OM.cjs';
|
|
9
9
|
export { ApiKeyCreditsProvider, createApiKeyProvider, verifyAdminApiKey, verifyBearerToken } from './auth/index.cjs';
|
|
10
10
|
export { CreditsService, DeductCreditsOptions, LowBalanceNotificationCallback, ReserveCreditsOptions, SubscriptionExpiredNotificationCallback, createCreditsService } from './service/index.cjs';
|
|
@@ -61,7 +61,7 @@ declare const creditSystemConfigSchema: z.ZodObject<{
|
|
|
61
61
|
*/
|
|
62
62
|
reservationExpiryMs: z.ZodDefault<z.ZodNumber>;
|
|
63
63
|
/**
|
|
64
|
-
* Default credits for new users (free tier)
|
|
64
|
+
* Default credits for new users (free tier). 0 means no signup grant.
|
|
65
65
|
*/
|
|
66
66
|
defaultFreeCredits: z.ZodDefault<z.ZodNumber>;
|
|
67
67
|
/**
|
package/dist/index.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { S as SubscriptionTier, T as TierConfig, a as PortableReservation } from './types-
|
|
2
|
-
export { A as AIProviderType, B as BuiltinTier, g as CreditCheckResult, C as CreditOperationType, b as CreditSource, D as DeductCreditsResult, J as JournalReferenceType, M as MonthlyResetResult, f as PortableJournalEntry, P as PortableTransaction, d as PortableUsageLog, c as PortableUserCredits, R as ReservationStatus, h as ResourceType, g as SDKCreditCheckResult, e as SubscriptionExpiryResult, i as TransactionType, U as UsageHistoryEntry, j as UsageHistoryResponse, c as UserCredits, W as WithCreditsOptions, k as calculateAvailableCredits, t as toDate, l as toPortableTimestamp } from './types-
|
|
3
|
-
export { A as AddCreditsAtomicOptions, a as AddCreditsOutcome, b as AddCreditsV2Input, C as CommitOutcome, c as CreateJournalEntryInput, d as CreateReservationInput, e as CreateTransactionInput, f as CreateUsageLogInput, g as CreditBalanceUpdate, h as CreditRepositoryFactory, E as ExpireOutcome,
|
|
4
|
-
export { CREDIT_AMOUNT_MAX, CREDIT_AMOUNT_SCALE, CreditError, CreditErrorCode, CreditErrorCodeType, PaymentEventPayload, RESERVATION_STATUSES, RESERVED_JOURNAL_KEY_PREFIX, ReservationIntegrityView, StoredPaymentEvent, TERMINAL_RESERVATION_STATUSES, assertDirectStatusWriteAllowed, assertHoldPlaced, assertKnownReservationStatus, assertPublicJournalKey, assertRepresentableAmount, assertRepresentableFields, assertRepresentableTierAmount, assertUnkeyedDirectReservation, assertUnreferencedDirectTransaction, assertValidCreditAmount, assertValidIdempotencyKey, assertValidStoredAmount, assertValidStoredAmountRaw, backedBalanceFloor, classifyDatabaseError, commitReservationWithJournal, createIdempotencyConflictError, createInsufficientCreditsError, createInvalidAmountError, createInvalidOperationTypeError, createPaymentRefConflictError, createReservationAlreadyProcessedError, createReservationExpiredError, createReservationNotFoundError, createTransientError, createUnsupportedOperationError, createUserNotFoundError, describePaymentMismatch, getSqlState, hasPlacedHold, isCreditError, isIdempotencyConflictError, isInsufficientCreditsError, isRepresentableAmount, isReservationStatus, isReservedJournalKey, isTerminalReservationStatus, isTransientDatabaseError, isTransientError, isUniqueViolation, isValidCreditAmount, isValidIdempotencyKey, normalizePaymentRef, numericToCents, releaseReservationWithJournal, reserveCreditsForOperation, sameAmount, storedMonthlyLimit, sumAmounts, terminalStatusOf, toCents } from './core/index.js';
|
|
1
|
+
import { S as SubscriptionTier, T as TierConfig, a as PortableReservation } from './types-MaViK5tD.js';
|
|
2
|
+
export { A as AIProviderType, B as BuiltinTier, g as CreditCheckResult, C as CreditOperationType, b as CreditSource, D as DeductCreditsResult, J as JournalReferenceType, M as MonthlyResetResult, f as PortableJournalEntry, P as PortableTransaction, d as PortableUsageLog, c as PortableUserCredits, R as ReservationStatus, h as ResourceType, g as SDKCreditCheckResult, e as SubscriptionExpiryResult, i as TransactionType, U as UsageHistoryEntry, j as UsageHistoryResponse, c as UserCredits, W as WithCreditsOptions, k as calculateAvailableCredits, t as toDate, l as toPortableTimestamp } from './types-MaViK5tD.js';
|
|
3
|
+
export { A as AddCreditsAtomicOptions, a as AddCreditsOutcome, b as AddCreditsV2Input, C as CommitOutcome, c as CreateJournalEntryInput, d as CreateReservationInput, e as CreateTransactionInput, f as CreateUsageLogInput, g as CreditBalanceUpdate, h as CreditPolicyOptions, i as CreditRepositoryFactory, E as ExpireOutcome, j as ExpireReservationV2Options, I as ICreditRepository, k as ICreditRepositoryCreditsV2, l as ICreditRepositoryV2, J as JournalEntryQuery, R as ReleaseOutcome, m as ReservationTransitionOptions, n as ReserveCreditsV2Input, o as ReserveOutcome, T as TerminalReservationStatus, p as TierUpdateInput, U as UsageLogQuery, q as isCreditedOutcome, r as isReservedOutcome, s as isWinningOutcome, t as reservationJournalKey, u as supportsCreditsV2, v as supportsIdempotentCredit, w as toClientUserCredits } from './types-3c-IAwZI.js';
|
|
4
|
+
export { CREDIT_AMOUNT_MAX, CREDIT_AMOUNT_SCALE, CreditError, CreditErrorCode, CreditErrorCodeType, PaymentEventPayload, RESERVATION_STATUSES, RESERVED_JOURNAL_KEY_PREFIX, ReservationIntegrityView, StoredPaymentEvent, TERMINAL_RESERVATION_STATUSES, assertDirectStatusWriteAllowed, assertHoldPlaced, assertKnownReservationStatus, assertPublicJournalKey, assertRepresentableAmount, assertRepresentableFields, assertRepresentableTierAmount, assertUnkeyedDirectReservation, assertUnreferencedDirectTransaction, assertValidBalanceTarget, assertValidCreditAmount, assertValidIdempotencyKey, assertValidStoredAmount, assertValidStoredAmountRaw, assertValidTierBalanceInput, attributeExpiredBacking, backedBalanceFloor, cappedBalance, classifyDatabaseError, commitReservationWithJournal, createIdempotencyConflictError, createInsufficientCreditsError, createInvalidAmountError, createInvalidOperationTypeError, createPaymentRefConflictError, createReservationAlreadyProcessedError, createReservationExpiredError, createReservationNotFoundError, createTransientError, createUnsupportedOperationError, createUserNotFoundError, describePaymentMismatch, expiredHoldBackingPatch, getSqlState, hasPlacedHold, isCreditError, isIdempotencyConflictError, isInsufficientCreditsError, isRepresentableAmount, isReservationStatus, isReservedJournalKey, isTerminalReservationStatus, isTransientDatabaseError, isTransientError, isUniqueViolation, isValidCreditAmount, isValidIdempotencyKey, normalizePaymentRef, numericToCents, planBalanceCap, planBalanceTarget, planHoldCommit, releaseReservationWithJournal, reserveCreditsForOperation, sameAmount, settleExpiredHoldBacking, spendableBalance, storedMonthlyLimit, sumAmounts, terminalStatusOf, toCents } from './core/index.js';
|
|
5
5
|
export { D as DeferredExecutor, c as createDeferredExecutor, g as genericDeferred, n as noopDeferred, s as synchronousDeferred } from './deferred-C7rrRfkm.js';
|
|
6
6
|
import { z } from 'zod';
|
|
7
|
-
export { InMemoryCreditRepository, addCreditsThroughRepository, commitThroughRepository, createInMemoryCreditRepository, generateId, getNextMonthlyReset, releaseThroughRepository, reserveThroughRepository } from './repository/index.js';
|
|
7
|
+
export { BalanceValidationResult, InMemoryCreditRepository, addCreditsThroughRepository, assertValidBalanceUpdate, commitThroughRepository, createInMemoryCreditRepository, generateId, getNextMonthlyReset, releaseThroughRepository, reserveThroughRepository, validateBalanceUpdate } from './repository/index.js';
|
|
8
8
|
export { C as CreditsAuthProviderFactory, a as CreditsUser, I as ICreditsAuthProvider } from './types-OZU3I5OM.js';
|
|
9
9
|
export { ApiKeyCreditsProvider, createApiKeyProvider, verifyAdminApiKey, verifyBearerToken } from './auth/index.js';
|
|
10
10
|
export { CreditsService, DeductCreditsOptions, LowBalanceNotificationCallback, ReserveCreditsOptions, SubscriptionExpiredNotificationCallback, createCreditsService } from './service/index.js';
|
|
@@ -61,7 +61,7 @@ declare const creditSystemConfigSchema: z.ZodObject<{
|
|
|
61
61
|
*/
|
|
62
62
|
reservationExpiryMs: z.ZodDefault<z.ZodNumber>;
|
|
63
63
|
/**
|
|
64
|
-
* Default credits for new users (free tier)
|
|
64
|
+
* Default credits for new users (free tier). 0 means no signup grant.
|
|
65
65
|
*/
|
|
66
66
|
defaultFreeCredits: z.ZodDefault<z.ZodNumber>;
|
|
67
67
|
/**
|
package/dist/index.js
CHANGED
|
@@ -219,6 +219,86 @@ function backedBalanceFloor(reserved, bonusCredits) {
|
|
|
219
219
|
assertRepresentableAmount(floor, "backedBalanceFloor", { reserved, bonusCredits });
|
|
220
220
|
return floor;
|
|
221
221
|
}
|
|
222
|
+
function cappedBalance(balance, cap, reserved, bonusCredits) {
|
|
223
|
+
return Math.min(balance, Math.max(cap, backedBalanceFloor(reserved, bonusCredits)));
|
|
224
|
+
}
|
|
225
|
+
function spendableBalance(balance, expiredHoldBacking = 0) {
|
|
226
|
+
return sumAmounts(balance, -Math.min(expiredHoldBacking, balance));
|
|
227
|
+
}
|
|
228
|
+
function planBalanceTarget(target, reserved, bonusCredits) {
|
|
229
|
+
const balance = Math.max(target, backedBalanceFloor(reserved, bonusCredits));
|
|
230
|
+
return { balance, expiredHoldBacking: sumAmounts(balance, -target) };
|
|
231
|
+
}
|
|
232
|
+
function planBalanceCap(balance, cap, reserved, bonusCredits, expiredHoldBacking = 0) {
|
|
233
|
+
const spendable = spendableBalance(balance, expiredHoldBacking);
|
|
234
|
+
const target = Math.min(spendable, cap);
|
|
235
|
+
const kept = Math.max(
|
|
236
|
+
sumAmounts(balance, -spendable),
|
|
237
|
+
sumAmounts(backedBalanceFloor(reserved, bonusCredits), -target)
|
|
238
|
+
);
|
|
239
|
+
const next = Math.min(balance, sumAmounts(target, kept));
|
|
240
|
+
return { balance: next, expiredHoldBacking: Math.max(0, sumAmounts(next, -target)) };
|
|
241
|
+
}
|
|
242
|
+
function settleExpiredHoldBacking(balance, expiredHoldBacking, holdBacking) {
|
|
243
|
+
const held = Math.min(expiredHoldBacking, balance);
|
|
244
|
+
const used = Math.min(held, holdBacking);
|
|
245
|
+
return { used, remaining: sumAmounts(held, -used) };
|
|
246
|
+
}
|
|
247
|
+
function planHoldCommit(balance, bonusCredits, expiredHoldBacking, holdBacking, amount) {
|
|
248
|
+
const backing = settleExpiredHoldBacking(balance, expiredHoldBacking, holdBacking);
|
|
249
|
+
const rest = sumAmounts(amount, -backing.used);
|
|
250
|
+
const fromSpendable = Math.min(spendableBalance(balance, expiredHoldBacking), rest);
|
|
251
|
+
const fromBonus = sumAmounts(rest, -fromSpendable);
|
|
252
|
+
return {
|
|
253
|
+
covered: fromBonus <= bonusCredits,
|
|
254
|
+
balance: sumAmounts(balance, -backing.used, -fromSpendable),
|
|
255
|
+
bonusCredits: sumAmounts(bonusCredits, -fromBonus),
|
|
256
|
+
expiredHoldBacking: backing.remaining
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
function attributeExpiredBacking(holds, total) {
|
|
260
|
+
const placed = holds.filter((hold) => hold.holdPlacedAt).sort(
|
|
261
|
+
(a, b) => a.holdPlacedAt === b.holdPlacedAt ? a.id < b.id ? -1 : 1 : a.holdPlacedAt < b.holdPlacedAt ? -1 : 1
|
|
262
|
+
);
|
|
263
|
+
const shares = /* @__PURE__ */ new Map();
|
|
264
|
+
let left = total;
|
|
265
|
+
for (const hold of placed) {
|
|
266
|
+
const kept = Math.min(hold.expiredBacking ?? 0, hold.amount, left);
|
|
267
|
+
shares.set(hold.id, kept);
|
|
268
|
+
left = sumAmounts(left, -kept);
|
|
269
|
+
}
|
|
270
|
+
for (const hold of placed) {
|
|
271
|
+
const share = shares.get(hold.id);
|
|
272
|
+
const added = Math.min(sumAmounts(hold.amount, -share), left);
|
|
273
|
+
shares.set(hold.id, sumAmounts(share, added));
|
|
274
|
+
left = sumAmounts(left, -added);
|
|
275
|
+
}
|
|
276
|
+
return shares;
|
|
277
|
+
}
|
|
278
|
+
function expiredHoldBackingPatch(next, stored = 0) {
|
|
279
|
+
return next > 0 || stored > 0 ? { expiredHoldBacking: next } : {};
|
|
280
|
+
}
|
|
281
|
+
function assertValidBalanceTarget(value, field, context) {
|
|
282
|
+
assertRepresentableAmount(value, field, context);
|
|
283
|
+
if (value < 0) {
|
|
284
|
+
throw new CreditError(
|
|
285
|
+
`${field} cannot be negative (got ${value})`,
|
|
286
|
+
CreditErrorCode.INVALID_AMOUNT,
|
|
287
|
+
{ field, value, ...context }
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
function assertValidTierBalanceInput(input, context) {
|
|
292
|
+
if (input.balanceCap === void 0) return;
|
|
293
|
+
if (input.balance !== void 0) {
|
|
294
|
+
throw new CreditError(
|
|
295
|
+
"A tier update cannot set both balance and balanceCap",
|
|
296
|
+
CreditErrorCode.INVALID_AMOUNT,
|
|
297
|
+
{ balance: input.balance, balanceCap: input.balanceCap, ...context }
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
assertValidBalanceTarget(input.balanceCap, "balanceCap", context);
|
|
301
|
+
}
|
|
222
302
|
function assertRepresentableTierAmount(value, field, context) {
|
|
223
303
|
if (value === Number.POSITIVE_INFINITY) return;
|
|
224
304
|
assertRepresentableAmount(value, field, context);
|
|
@@ -627,9 +707,9 @@ var creditSystemConfigSchema = z.object({
|
|
|
627
707
|
*/
|
|
628
708
|
reservationExpiryMs: z.number().positive().default(5 * 60 * 1e3),
|
|
629
709
|
/**
|
|
630
|
-
* Default credits for new users (free tier)
|
|
710
|
+
* Default credits for new users (free tier). 0 means no signup grant.
|
|
631
711
|
*/
|
|
632
|
-
defaultFreeCredits: z.number().
|
|
712
|
+
defaultFreeCredits: z.number().min(0).default(25),
|
|
633
713
|
/**
|
|
634
714
|
* Grace period in days after subscription expires before downgrade
|
|
635
715
|
*/
|
|
@@ -929,6 +1009,8 @@ function toClientUserCredits(credits) {
|
|
|
929
1009
|
monthlyUsed: credits.monthlyUsed,
|
|
930
1010
|
monthlyResetAt: toISOString(credits.monthlyResetAt),
|
|
931
1011
|
subscriptionExpiresAt: credits.subscriptionExpiresAt ? toISOString(credits.subscriptionExpiresAt) : null,
|
|
1012
|
+
...credits.expiredHoldBacking !== void 0 ? { expiredHoldBacking: credits.expiredHoldBacking } : {},
|
|
1013
|
+
...credits.signupGrant !== void 0 ? { signupGrant: credits.signupGrant } : {},
|
|
932
1014
|
createdAt: toISOString(credits.createdAt),
|
|
933
1015
|
updatedAt: toISOString(credits.updatedAt)
|
|
934
1016
|
};
|
|
@@ -1160,11 +1242,36 @@ function toDate2(value) {
|
|
|
1160
1242
|
return /* @__PURE__ */ new Date();
|
|
1161
1243
|
}
|
|
1162
1244
|
function getNextMonthlyReset(from = /* @__PURE__ */ new Date()) {
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
next
|
|
1167
|
-
|
|
1245
|
+
return new Date(from.getFullYear(), from.getMonth() + 1, 1);
|
|
1246
|
+
}
|
|
1247
|
+
function validateBalanceUpdate(current, updates) {
|
|
1248
|
+
const next = (base, absolute, increment) => sumAmounts(absolute ?? base, increment ?? 0);
|
|
1249
|
+
const balance = next(current.balance, updates.balance, updates.balanceIncrement);
|
|
1250
|
+
const bonusCredits = next(
|
|
1251
|
+
current.bonusCredits ?? 0,
|
|
1252
|
+
updates.bonusCredits,
|
|
1253
|
+
updates.bonusCreditsIncrement
|
|
1254
|
+
);
|
|
1255
|
+
const reserved = next(current.reserved ?? 0, updates.reserved, updates.reservedIncrement);
|
|
1256
|
+
const errors = [];
|
|
1257
|
+
if (balance < 0) errors.push(`Balance cannot be negative: ${balance}`);
|
|
1258
|
+
const marked = updates.balance === void 0 ? current.expiredHoldBacking ?? 0 : 0;
|
|
1259
|
+
if ((updates.balanceIncrement ?? 0) < 0 && balance >= 0 && balance < marked) {
|
|
1260
|
+
errors.push(`Balance (${balance}) cannot go below expired hold backing (${marked})`);
|
|
1261
|
+
}
|
|
1262
|
+
if (bonusCredits < 0) errors.push(`BonusCredits cannot be negative: ${bonusCredits}`);
|
|
1263
|
+
if (reserved < 0) errors.push(`Reserved cannot be negative: ${reserved}`);
|
|
1264
|
+
const covering = sumAmounts(balance, bonusCredits);
|
|
1265
|
+
if (reserved > covering) {
|
|
1266
|
+
errors.push(`Reserved (${reserved}) exceeds balance + bonusCredits (${covering})`);
|
|
1267
|
+
}
|
|
1268
|
+
return { valid: errors.length === 0, errors };
|
|
1269
|
+
}
|
|
1270
|
+
function assertValidBalanceUpdate(current, updates) {
|
|
1271
|
+
const result = validateBalanceUpdate(current, updates);
|
|
1272
|
+
if (!result.valid) {
|
|
1273
|
+
throw new Error(`Invalid balance update: ${result.errors.join("; ")}`);
|
|
1274
|
+
}
|
|
1168
1275
|
}
|
|
1169
1276
|
|
|
1170
1277
|
// src/repository/memory/snapshot.ts
|
|
@@ -1344,6 +1451,22 @@ function assertTrustworthy(reservation, transition) {
|
|
|
1344
1451
|
assertLockedAmount(reservation, transition);
|
|
1345
1452
|
assertHoldPlaced(reservation, transition);
|
|
1346
1453
|
}
|
|
1454
|
+
function openHolds(store, userId) {
|
|
1455
|
+
return [...store.reservations.get(userId)?.values() ?? []].filter(
|
|
1456
|
+
(reservation) => reservation.status === "reserved"
|
|
1457
|
+
);
|
|
1458
|
+
}
|
|
1459
|
+
function writeExpiredHoldBacking(store, credits, next) {
|
|
1460
|
+
const stored = credits.expiredHoldBacking;
|
|
1461
|
+
if (next <= 0 && !stored) return;
|
|
1462
|
+
const holds = openHolds(store, credits.userId);
|
|
1463
|
+
const shares = attributeExpiredBacking(holds, next);
|
|
1464
|
+
for (const hold of holds) {
|
|
1465
|
+
const share = shares.get(hold.id) ?? 0;
|
|
1466
|
+
if (share !== (hold.expiredBacking ?? 0)) hold.expiredBacking = share;
|
|
1467
|
+
}
|
|
1468
|
+
Object.assign(credits, expiredHoldBackingPatch(sumAmounts(...shares.values()), stored));
|
|
1469
|
+
}
|
|
1347
1470
|
function getReservation(store, userId, reservationId) {
|
|
1348
1471
|
return store.reservations.get(userId)?.get(reservationId);
|
|
1349
1472
|
}
|
|
@@ -1441,16 +1564,22 @@ async function commitReservationV2(store, userId, reservationId, options) {
|
|
|
1441
1564
|
const amount = reservation.amount;
|
|
1442
1565
|
await yieldPoint(store);
|
|
1443
1566
|
assertReservedCoversHold(credits, userId, reservationId, amount);
|
|
1444
|
-
|
|
1567
|
+
const paid = planHoldCommit(
|
|
1568
|
+
credits.balance,
|
|
1569
|
+
credits.bonusCredits,
|
|
1570
|
+
credits.expiredHoldBacking ?? 0,
|
|
1571
|
+
reservation.expiredBacking ?? 0,
|
|
1572
|
+
amount
|
|
1573
|
+
);
|
|
1574
|
+
if (!paid.covered) {
|
|
1445
1575
|
throw new CreditError(
|
|
1446
1576
|
`Insufficient credits to commit reservation ${reservationId}`,
|
|
1447
1577
|
CreditErrorCode.INSUFFICIENT_CREDITS,
|
|
1448
1578
|
{ userId, reservationId, required: amount }
|
|
1449
1579
|
);
|
|
1450
1580
|
}
|
|
1451
|
-
const
|
|
1452
|
-
const
|
|
1453
|
-
const nextBonusCredits = sumAmounts(credits.bonusCredits, -sumAmounts(amount, -fromBalance));
|
|
1581
|
+
const nextBalance = paid.balance;
|
|
1582
|
+
const nextBonusCredits = paid.bonusCredits;
|
|
1454
1583
|
const balanceAfter = sumAmounts(nextBalance, nextBonusCredits);
|
|
1455
1584
|
const journal = {
|
|
1456
1585
|
userId,
|
|
@@ -1484,6 +1613,10 @@ async function commitReservationV2(store, userId, reservationId, options) {
|
|
|
1484
1613
|
credits.bonusCredits = nextBonusCredits;
|
|
1485
1614
|
credits.reserved = nextReserved;
|
|
1486
1615
|
credits.monthlyUsed = nextMonthlyUsed;
|
|
1616
|
+
Object.assign(
|
|
1617
|
+
credits,
|
|
1618
|
+
expiredHoldBackingPatch(paid.expiredHoldBacking, credits.expiredHoldBacking)
|
|
1619
|
+
);
|
|
1487
1620
|
credits.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1488
1621
|
const journalEntryId = commitTransitionJournal(store, journal, existingEntryId);
|
|
1489
1622
|
return {
|
|
@@ -1563,19 +1696,26 @@ function finishUnspent(store, reservation, transition) {
|
|
|
1563
1696
|
reservation.id,
|
|
1564
1697
|
reservation.amount
|
|
1565
1698
|
);
|
|
1699
|
+
const backing = settleExpiredHoldBacking(
|
|
1700
|
+
credits.balance,
|
|
1701
|
+
credits.expiredHoldBacking ?? 0,
|
|
1702
|
+
reservation.expiredBacking ?? 0
|
|
1703
|
+
);
|
|
1704
|
+
const nextBalance = sumAmounts(credits.balance, -backing.used);
|
|
1566
1705
|
const journal = {
|
|
1567
1706
|
userId: reservation.userId,
|
|
1568
1707
|
operation: `${transition.transition}Reservation`,
|
|
1569
1708
|
entryType: "credit",
|
|
1570
1709
|
amount: 0,
|
|
1571
|
-
balanceAfter: sumAmounts(
|
|
1710
|
+
balanceAfter: sumAmounts(nextBalance, credits.bonusCredits),
|
|
1572
1711
|
source: transition.source,
|
|
1573
1712
|
reservationId: reservation.id,
|
|
1574
1713
|
description: transition.options?.description ?? `${transition.verb} ${reservation.amount} reserved credits for ${getOperationLabel(reservation.operationType)}`,
|
|
1575
1714
|
metadata: {
|
|
1576
1715
|
...transition.options?.metadata,
|
|
1577
1716
|
operationType: reservation.operationType,
|
|
1578
|
-
amount: reservation.amount
|
|
1717
|
+
amount: reservation.amount,
|
|
1718
|
+
...backing.used > 0 ? { expiredCreditsRemoved: backing.used } : {}
|
|
1579
1719
|
},
|
|
1580
1720
|
idempotencyKey: reservationJournalKey(reservation.id, transition.transition)
|
|
1581
1721
|
};
|
|
@@ -1588,6 +1728,8 @@ function finishUnspent(store, reservation, transition) {
|
|
|
1588
1728
|
reservation.status = transition.status;
|
|
1589
1729
|
reservation.completedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1590
1730
|
credits.reserved = nextReserved;
|
|
1731
|
+
credits.balance = nextBalance;
|
|
1732
|
+
Object.assign(credits, expiredHoldBackingPatch(backing.remaining, credits.expiredHoldBacking));
|
|
1591
1733
|
credits.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1592
1734
|
return commitTransitionJournal(store, journal, existingEntryId);
|
|
1593
1735
|
}
|
|
@@ -1598,6 +1740,10 @@ function isoOf(value) {
|
|
|
1598
1740
|
}
|
|
1599
1741
|
var InMemoryCreditRepository = class {
|
|
1600
1742
|
store = new MemoryStore();
|
|
1743
|
+
options;
|
|
1744
|
+
constructor(options = {}) {
|
|
1745
|
+
this.options = options;
|
|
1746
|
+
}
|
|
1601
1747
|
// ==================== User Credits ====================
|
|
1602
1748
|
/**
|
|
1603
1749
|
* A copy, never the live record.
|
|
@@ -1614,7 +1760,8 @@ var InMemoryCreditRepository = class {
|
|
|
1614
1760
|
return copyRecord(credits) ?? null;
|
|
1615
1761
|
}
|
|
1616
1762
|
async initializeUserCredits(userId, tier, initialBalance) {
|
|
1617
|
-
|
|
1763
|
+
const existing = this.store.users.get(userId);
|
|
1764
|
+
return copyRecord(existing ?? this.seedUser(userId, tier, initialBalance));
|
|
1618
1765
|
}
|
|
1619
1766
|
/**
|
|
1620
1767
|
* The synchronous body of `initializeUserCredits`.
|
|
@@ -1665,8 +1812,11 @@ var InMemoryCreditRepository = class {
|
|
|
1665
1812
|
if (!credits) {
|
|
1666
1813
|
throw new Error(`User ${userId} not found`);
|
|
1667
1814
|
}
|
|
1815
|
+
assertValidBalanceUpdate(credits, updates);
|
|
1668
1816
|
const next = { ...credits };
|
|
1669
|
-
if (updates.balance !== void 0)
|
|
1817
|
+
if (updates.balance !== void 0) {
|
|
1818
|
+
next.balance = updates.balance;
|
|
1819
|
+
}
|
|
1670
1820
|
if (updates.bonusCredits !== void 0) next.bonusCredits = updates.bonusCredits;
|
|
1671
1821
|
if (updates.reserved !== void 0) next.reserved = updates.reserved;
|
|
1672
1822
|
if (updates.tier !== void 0) next.tier = updates.tier;
|
|
@@ -1703,6 +1853,9 @@ var InMemoryCreditRepository = class {
|
|
|
1703
1853
|
userId,
|
|
1704
1854
|
operation: "updateUserCredits"
|
|
1705
1855
|
});
|
|
1856
|
+
if ((credits.expiredHoldBacking ?? 0) > 0 && updates.balance !== void 0) {
|
|
1857
|
+
writeExpiredHoldBacking(this.store, next, 0);
|
|
1858
|
+
}
|
|
1706
1859
|
next.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1707
1860
|
this.store.users.set(userId, next);
|
|
1708
1861
|
}
|
|
@@ -1711,17 +1864,28 @@ var InMemoryCreditRepository = class {
|
|
|
1711
1864
|
{ monthlyLimit: input.monthlyLimit, balance: input.balance, monthlyUsed: input.monthlyUsed },
|
|
1712
1865
|
{ userId, operation: "updateUserTier" }
|
|
1713
1866
|
);
|
|
1867
|
+
assertValidTierBalanceInput(input, { userId, operation: "updateUserTier" });
|
|
1714
1868
|
const credits = this.store.users.get(userId);
|
|
1715
1869
|
if (!credits) {
|
|
1716
1870
|
throw new Error(`User ${userId} not found`);
|
|
1717
1871
|
}
|
|
1872
|
+
if (input.balanceCap !== void 0 && !isFreeTier(credits.tier)) {
|
|
1873
|
+
const capped = planBalanceCap(
|
|
1874
|
+
credits.balance,
|
|
1875
|
+
input.balanceCap,
|
|
1876
|
+
credits.reserved,
|
|
1877
|
+
credits.bonusCredits,
|
|
1878
|
+
credits.expiredHoldBacking
|
|
1879
|
+
);
|
|
1880
|
+
credits.balance = capped.balance;
|
|
1881
|
+
writeExpiredHoldBacking(this.store, credits, capped.expiredHoldBacking);
|
|
1882
|
+
}
|
|
1718
1883
|
credits.tier = input.tier;
|
|
1719
1884
|
credits.monthlyLimit = input.monthlyLimit;
|
|
1720
1885
|
if (input.balance !== void 0) {
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
);
|
|
1886
|
+
const planned = planBalanceTarget(input.balance, credits.reserved, credits.bonusCredits);
|
|
1887
|
+
credits.balance = planned.balance;
|
|
1888
|
+
writeExpiredHoldBacking(this.store, credits, planned.expiredHoldBacking);
|
|
1725
1889
|
}
|
|
1726
1890
|
if (input.monthlyUsed !== void 0) credits.monthlyUsed = input.monthlyUsed;
|
|
1727
1891
|
if (input.subscriptionExpiresAt !== void 0) {
|
|
@@ -1942,13 +2106,17 @@ var InMemoryCreditRepository = class {
|
|
|
1942
2106
|
if (!credits) {
|
|
1943
2107
|
throw new Error(`User credits not found for userId: ${userId}`);
|
|
1944
2108
|
}
|
|
1945
|
-
const
|
|
2109
|
+
const spendable = spendableBalance(credits.balance, credits.expiredHoldBacking);
|
|
2110
|
+
const available = Math.min(
|
|
2111
|
+
sumAmounts(credits.balance, credits.bonusCredits, -credits.reserved),
|
|
2112
|
+
sumAmounts(spendable, credits.bonusCredits)
|
|
2113
|
+
);
|
|
1946
2114
|
if (available < amount) {
|
|
1947
2115
|
throw new Error(
|
|
1948
2116
|
`Insufficient credits. Available: ${available}, requested: ${amount}`
|
|
1949
2117
|
);
|
|
1950
2118
|
}
|
|
1951
|
-
const balanceDeduction = Math.min(
|
|
2119
|
+
const balanceDeduction = Math.min(spendable, amount);
|
|
1952
2120
|
const bonusDeduction = sumAmounts(amount, -balanceDeduction);
|
|
1953
2121
|
const previousBalance = sumAmounts(credits.balance, credits.bonusCredits);
|
|
1954
2122
|
const nextBalance = sumAmounts(credits.balance, -balanceDeduction);
|
|
@@ -2185,7 +2353,11 @@ var InMemoryCreditRepository = class {
|
|
|
2185
2353
|
return { expiredCount, creditsReleased, errors };
|
|
2186
2354
|
}
|
|
2187
2355
|
// ==================== Atomic Monthly Reset ====================
|
|
2188
|
-
|
|
2356
|
+
/**
|
|
2357
|
+
* @param _tier - Not trusted. The caller read it earlier and the account may
|
|
2358
|
+
* have changed tier since, so the reset uses the tier stored on the account.
|
|
2359
|
+
*/
|
|
2360
|
+
async atomicMonthlyReset(userId, _tier, expectedResetAt) {
|
|
2189
2361
|
const credits = this.store.users.get(userId);
|
|
2190
2362
|
if (!credits) {
|
|
2191
2363
|
throw new Error(`User ${userId} not found`);
|
|
@@ -2195,17 +2367,41 @@ var InMemoryCreditRepository = class {
|
|
|
2195
2367
|
if (currentResetAt !== expected) {
|
|
2196
2368
|
return { wasReset: false, credits: { ...credits } };
|
|
2197
2369
|
}
|
|
2198
|
-
|
|
2370
|
+
const tier = credits.tier;
|
|
2371
|
+
const spendable = spendableBalance(credits.balance, credits.expiredHoldBacking);
|
|
2372
|
+
const decision = this.options.resolveMonthlyReset?.({ tier, balance: spendable });
|
|
2373
|
+
if (decision === null) {
|
|
2374
|
+
return { wasReset: false, credits: { ...credits } };
|
|
2375
|
+
}
|
|
2376
|
+
if (decision) {
|
|
2377
|
+
assertValidBalanceTarget(decision.balance, "resolveMonthlyReset balance", { userId, tier });
|
|
2378
|
+
if (decision.monthlyLimit !== void 0) {
|
|
2379
|
+
assertValidBalanceTarget(decision.monthlyLimit, "resolveMonthlyReset monthlyLimit", {
|
|
2380
|
+
userId,
|
|
2381
|
+
tier
|
|
2382
|
+
});
|
|
2383
|
+
}
|
|
2384
|
+
} else {
|
|
2385
|
+
assertRepresentableTierAmount(getConfigMonthlyLimit(tier), "monthlyLimit", { userId, tier });
|
|
2386
|
+
}
|
|
2199
2387
|
const nextReset = getNextMonthlyReset();
|
|
2200
|
-
const target = monthlyResetBalance(tier);
|
|
2201
|
-
const floor = backedBalanceFloor(credits.reserved, credits.bonusCredits);
|
|
2202
2388
|
const previousBalance = credits.balance;
|
|
2203
|
-
|
|
2389
|
+
let target;
|
|
2390
|
+
if (decision) {
|
|
2391
|
+
target = decision.balance;
|
|
2392
|
+
} else {
|
|
2393
|
+
const configured = monthlyResetBalance(tier);
|
|
2394
|
+
target = configured.kind === "atLeast" ? Math.max(spendable, configured.value) : configured.value;
|
|
2395
|
+
}
|
|
2396
|
+
const planned = planBalanceTarget(target, credits.reserved, credits.bonusCredits);
|
|
2397
|
+
const newBalance = planned.balance;
|
|
2204
2398
|
const change = sumAmounts(newBalance, -previousBalance);
|
|
2205
2399
|
const balanceAfter = sumAmounts(newBalance, credits.bonusCredits);
|
|
2206
2400
|
assertRepresentableAmount(Math.abs(change), "journal amount", { userId });
|
|
2207
2401
|
assertRepresentableAmount(balanceAfter, "journal balanceAfter", { userId });
|
|
2208
2402
|
credits.balance = newBalance;
|
|
2403
|
+
writeExpiredHoldBacking(this.store, credits, planned.expiredHoldBacking);
|
|
2404
|
+
if (decision?.monthlyLimit !== void 0) credits.monthlyLimit = decision.monthlyLimit;
|
|
2209
2405
|
credits.monthlyUsed = 0;
|
|
2210
2406
|
credits.monthlyResetAt = nextReset.toISOString();
|
|
2211
2407
|
credits.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -2259,23 +2455,34 @@ var InMemoryCreditRepository = class {
|
|
|
2259
2455
|
};
|
|
2260
2456
|
}
|
|
2261
2457
|
const defaultTier = getDefaultTier();
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
tier: defaultTier
|
|
2266
|
-
}
|
|
2267
|
-
|
|
2458
|
+
let limit;
|
|
2459
|
+
if (this.options.getDowngradeBalanceCap) {
|
|
2460
|
+
limit = this.options.getDowngradeBalanceCap();
|
|
2461
|
+
assertValidBalanceTarget(limit, "getDowngradeBalanceCap", { userId, tier: defaultTier });
|
|
2462
|
+
} else {
|
|
2463
|
+
const configured = getConfigMonthlyLimit(defaultTier);
|
|
2464
|
+
assertRepresentableTierAmount(configured, "monthlyLimit", {
|
|
2465
|
+
userId,
|
|
2466
|
+
tier: defaultTier
|
|
2467
|
+
});
|
|
2468
|
+
limit = storedMonthlyLimit(configured);
|
|
2469
|
+
}
|
|
2268
2470
|
const previousTier = credits.tier;
|
|
2269
2471
|
const previousBalance = credits.balance;
|
|
2270
|
-
const
|
|
2271
|
-
|
|
2272
|
-
|
|
2472
|
+
const planned = planBalanceCap(
|
|
2473
|
+
credits.balance,
|
|
2474
|
+
limit,
|
|
2475
|
+
credits.reserved,
|
|
2476
|
+
credits.bonusCredits,
|
|
2477
|
+
credits.expiredHoldBacking
|
|
2273
2478
|
);
|
|
2479
|
+
const newBalance = planned.balance;
|
|
2274
2480
|
const balanceAfter = sumAmounts(newBalance, credits.bonusCredits);
|
|
2275
2481
|
assertRepresentableAmount(balanceAfter, "journal balanceAfter", { userId });
|
|
2276
2482
|
credits.tier = defaultTier;
|
|
2277
2483
|
credits.monthlyLimit = limit;
|
|
2278
2484
|
credits.balance = newBalance;
|
|
2485
|
+
writeExpiredHoldBacking(this.store, credits, planned.expiredHoldBacking);
|
|
2279
2486
|
credits.subscriptionExpiresAt = null;
|
|
2280
2487
|
credits.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2281
2488
|
this.store.users.set(userId, credits);
|
|
@@ -2333,8 +2540,8 @@ var InMemoryCreditRepository = class {
|
|
|
2333
2540
|
return copyRecords(Array.from(userReservations.values()));
|
|
2334
2541
|
}
|
|
2335
2542
|
};
|
|
2336
|
-
function createInMemoryCreditRepository() {
|
|
2337
|
-
return new InMemoryCreditRepository();
|
|
2543
|
+
function createInMemoryCreditRepository(options = {}) {
|
|
2544
|
+
return new InMemoryCreditRepository(options);
|
|
2338
2545
|
}
|
|
2339
2546
|
|
|
2340
2547
|
// src/auth/api-key.ts
|
|
@@ -2497,9 +2704,11 @@ var CreditsService = class {
|
|
|
2497
2704
|
source: "monthly_reset",
|
|
2498
2705
|
referenceId: `reset-${Date.now()}`,
|
|
2499
2706
|
referenceType: "reset",
|
|
2500
|
-
|
|
2707
|
+
// The tier the reset ran on, which a concurrent downgrade may have
|
|
2708
|
+
// changed since `data` was read.
|
|
2709
|
+
description: `Monthly credit reset for ${resetResult.credits.tier} tier.`,
|
|
2501
2710
|
metadata: {
|
|
2502
|
-
tier:
|
|
2711
|
+
tier: resetResult.credits.tier,
|
|
2503
2712
|
previousBalance: data.balance,
|
|
2504
2713
|
newBalance: resetResult.credits.balance
|
|
2505
2714
|
}
|
|
@@ -3785,12 +3994,17 @@ export {
|
|
|
3785
3994
|
assertRepresentableTierAmount,
|
|
3786
3995
|
assertUnkeyedDirectReservation,
|
|
3787
3996
|
assertUnreferencedDirectTransaction,
|
|
3997
|
+
assertValidBalanceTarget,
|
|
3998
|
+
assertValidBalanceUpdate,
|
|
3788
3999
|
assertValidCreditAmount,
|
|
3789
4000
|
assertValidIdempotencyKey,
|
|
3790
4001
|
assertValidStoredAmount,
|
|
3791
4002
|
assertValidStoredAmountRaw,
|
|
4003
|
+
assertValidTierBalanceInput,
|
|
4004
|
+
attributeExpiredBacking,
|
|
3792
4005
|
backedBalanceFloor,
|
|
3793
4006
|
calculateAvailableCredits,
|
|
4007
|
+
cappedBalance,
|
|
3794
4008
|
checkAndNotifyLowBalance,
|
|
3795
4009
|
classifyDatabaseError,
|
|
3796
4010
|
clearConfigProvider,
|
|
@@ -3816,6 +4030,7 @@ export {
|
|
|
3816
4030
|
createUnsupportedOperationError,
|
|
3817
4031
|
createUserNotFoundError,
|
|
3818
4032
|
describePaymentMismatch,
|
|
4033
|
+
expiredHoldBackingPatch,
|
|
3819
4034
|
generateId,
|
|
3820
4035
|
generateRequestId2 as generateRequestId,
|
|
3821
4036
|
genericDeferred,
|
|
@@ -3874,6 +4089,9 @@ export {
|
|
|
3874
4089
|
parseApiError,
|
|
3875
4090
|
parseCreditError,
|
|
3876
4091
|
parseTier,
|
|
4092
|
+
planBalanceCap,
|
|
4093
|
+
planBalanceTarget,
|
|
4094
|
+
planHoldCommit,
|
|
3877
4095
|
registerConfigProvider,
|
|
3878
4096
|
registerNotificationHandler,
|
|
3879
4097
|
releaseReservationWithJournal,
|
|
@@ -3883,6 +4101,8 @@ export {
|
|
|
3883
4101
|
reserveThroughRepository,
|
|
3884
4102
|
resetConfig,
|
|
3885
4103
|
sameAmount,
|
|
4104
|
+
settleExpiredHoldBacking,
|
|
4105
|
+
spendableBalance,
|
|
3886
4106
|
storedMonthlyLimit,
|
|
3887
4107
|
sumAmounts,
|
|
3888
4108
|
supportsCreditsV2,
|
|
@@ -3894,6 +4114,7 @@ export {
|
|
|
3894
4114
|
toClientUserCredits,
|
|
3895
4115
|
toDate,
|
|
3896
4116
|
toPortableTimestamp,
|
|
4117
|
+
validateBalanceUpdate,
|
|
3897
4118
|
verifyAdminApiKey,
|
|
3898
4119
|
verifyBearerToken
|
|
3899
4120
|
};
|