@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/core/index.d.cts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { R as ReservationStatus, a as PortableReservation, P as PortableTransaction, b as CreditSource, J as JournalReferenceType } from '../types-
|
|
2
|
-
export { A as AIProviderType, B as BuiltinTier, g as CreditCheckResult, C as CreditOperationType, D as DeductCreditsResult, M as MonthlyResetResult, f as PortableJournalEntry, d as PortableUsageLog, c as PortableUserCredits, h as ResourceType, e as SubscriptionExpiryResult, S as SubscriptionTier, T as TierConfig, i as TransactionType, U as UsageHistoryEntry, j as UsageHistoryResponse, W as WithCreditsOptions, k as calculateAvailableCredits, t as toDate, l as toPortableTimestamp } from '../types-
|
|
3
|
-
import { T as TerminalReservationStatus, I as ICreditRepository } from '../types-
|
|
4
|
-
export { a as AddCreditsOutcome, C as CommitOutcome, E as ExpireOutcome, R as ReleaseOutcome,
|
|
1
|
+
import { R as ReservationStatus, a as PortableReservation, P as PortableTransaction, b as CreditSource, J as JournalReferenceType } from '../types-MaViK5tD.cjs';
|
|
2
|
+
export { A as AIProviderType, B as BuiltinTier, g as CreditCheckResult, C as CreditOperationType, D as DeductCreditsResult, M as MonthlyResetResult, f as PortableJournalEntry, d as PortableUsageLog, c as PortableUserCredits, h as ResourceType, e as SubscriptionExpiryResult, S as SubscriptionTier, T as TierConfig, i as TransactionType, U as UsageHistoryEntry, j as UsageHistoryResponse, W as WithCreditsOptions, k as calculateAvailableCredits, t as toDate, l as toPortableTimestamp } from '../types-MaViK5tD.cjs';
|
|
3
|
+
import { T as TerminalReservationStatus, I as ICreditRepository } from '../types-XSjNXO5l.cjs';
|
|
4
|
+
export { a as AddCreditsOutcome, C as CommitOutcome, E as ExpireOutcome, R as ReleaseOutcome, o as ReserveOutcome, q as isCreditedOutcome, r as isReservedOutcome, s as isWinningOutcome } from '../types-XSjNXO5l.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
|
|
|
7
7
|
/**
|
|
@@ -393,6 +393,7 @@ declare function createPaymentRefConflictError(paymentRef: string, details: {
|
|
|
393
393
|
* These live in core rather than in an adapter so every repository — shipped
|
|
394
394
|
* or third-party — validates identically.
|
|
395
395
|
*/
|
|
396
|
+
|
|
396
397
|
/** `numeric(12, 2)`: 12 significant digits, 2 of them after the point. */
|
|
397
398
|
declare const CREDIT_AMOUNT_SCALE = 2;
|
|
398
399
|
/** Largest representable amount: ten integer digits plus two decimals. */
|
|
@@ -479,6 +480,92 @@ declare function assertRepresentableFields(fields: Record<string, unknown>, cont
|
|
|
479
480
|
* fund commits, so they count toward the backing.
|
|
480
481
|
*/
|
|
481
482
|
declare function backedBalanceFloor(reserved: number, bonusCredits: number): number;
|
|
483
|
+
/**
|
|
484
|
+
* The balance left by a cap: lowered to `cap` at most, never raised, and never
|
|
485
|
+
* cut through the credits backing outstanding holds (see
|
|
486
|
+
* {@link backedBalanceFloor}). A balance already under `cap` is left alone,
|
|
487
|
+
* which is the point of computing this from the stored row inside a transaction
|
|
488
|
+
* rather than writing a value the caller read earlier.
|
|
489
|
+
*/
|
|
490
|
+
declare function cappedBalance(balance: number, cap: number, reserved: number, bonusCredits: number): number;
|
|
491
|
+
/** A balance a lowering write lands on, and how much of it only backs holds. */
|
|
492
|
+
interface PlannedBalance {
|
|
493
|
+
balance: number;
|
|
494
|
+
expiredHoldBacking: number;
|
|
495
|
+
}
|
|
496
|
+
/** The part of a balance not kept only for holds. A missing field reads as 0. */
|
|
497
|
+
declare function spendableBalance(balance: number, expiredHoldBacking?: number): number;
|
|
498
|
+
/**
|
|
499
|
+
* An absolute balance write: `target`, floored at what backs outstanding holds.
|
|
500
|
+
* Whatever the floor kept above `target` is not the account's to spend.
|
|
501
|
+
*/
|
|
502
|
+
declare function planBalanceTarget(target: number, reserved: number, bonusCredits: number): PlannedBalance;
|
|
503
|
+
/**
|
|
504
|
+
* A cap on the spendable part of a balance. Like {@link cappedBalance} it never
|
|
505
|
+
* raises the balance and never cuts through what backs holds, and it records the
|
|
506
|
+
* part kept above the cap so a later release cannot hand it back. Credits already
|
|
507
|
+
* marked stay marked, so applying a cap twice changes nothing.
|
|
508
|
+
*/
|
|
509
|
+
declare function planBalanceCap(balance: number, cap: number, reserved: number, bonusCredits: number, expiredHoldBacking?: number): PlannedBalance;
|
|
510
|
+
/**
|
|
511
|
+
* What a closing hold takes out of the credits kept only for holds: its own share
|
|
512
|
+
* and nothing of another hold's. `holdBacking` comes from
|
|
513
|
+
* {@link attributeExpiredBacking}.
|
|
514
|
+
*/
|
|
515
|
+
declare function settleExpiredHoldBacking(balance: number, expiredHoldBacking: number, holdBacking: number): {
|
|
516
|
+
used: number;
|
|
517
|
+
remaining: number;
|
|
518
|
+
};
|
|
519
|
+
/** The balances a commit lands on, and whether the account could pay it. */
|
|
520
|
+
interface PlannedHoldCommit {
|
|
521
|
+
covered: boolean;
|
|
522
|
+
balance: number;
|
|
523
|
+
bonusCredits: number;
|
|
524
|
+
expiredHoldBacking: number;
|
|
525
|
+
}
|
|
526
|
+
/**
|
|
527
|
+
* Pay a hold: first its own share of the marked credits, then spendable balance,
|
|
528
|
+
* then bonus. Marked credits backing other holds are never touched, so what this
|
|
529
|
+
* commit costs does not depend on which holds closed before it.
|
|
530
|
+
*/
|
|
531
|
+
declare function planHoldCommit(balance: number, bonusCredits: number, expiredHoldBacking: number, holdBacking: number, amount: number): PlannedHoldCommit;
|
|
532
|
+
/** The fields of an open hold that decide its share of the marked credits. */
|
|
533
|
+
type ExpiredBackingHold = Pick<PortableReservation, "id" | "amount" | "expiredBacking" | "holdPlacedAt">;
|
|
534
|
+
/**
|
|
535
|
+
* Split `total` marked credits across open holds, returning each hold's share.
|
|
536
|
+
*
|
|
537
|
+
* Shares a hold already has are kept first, oldest hold first, so lowering `total`
|
|
538
|
+
* trims the newest and raising it never moves credits off a hold. Only what is left
|
|
539
|
+
* goes to holds with room, again oldest first, which makes the split a function of
|
|
540
|
+
* the holds and not of the order they later close in. A hold never carries more than
|
|
541
|
+
* its amount. A row without `holdPlacedAt` is a record, not a hold, and gets nothing.
|
|
542
|
+
*
|
|
543
|
+
* The account's `expiredHoldBacking` is written as the sum of these shares, in the
|
|
544
|
+
* same write, so what `total` could not attribute is not marked. Such credits stay out
|
|
545
|
+
* of reach anyway, because the `reserved` that kept them still counts against them.
|
|
546
|
+
*/
|
|
547
|
+
declare function attributeExpiredBacking(holds: readonly ExpiredBackingHold[], total: number): Map<string, number>;
|
|
548
|
+
/**
|
|
549
|
+
* The field to write for a new backing value: nothing when it was and stays 0, so
|
|
550
|
+
* an account that never had any keeps a document without the field.
|
|
551
|
+
*/
|
|
552
|
+
declare function expiredHoldBackingPatch(next: number, stored?: number): {
|
|
553
|
+
expiredHoldBacking?: number;
|
|
554
|
+
};
|
|
555
|
+
/**
|
|
556
|
+
* Validate a balance target supplied by the application: a cap or the result of
|
|
557
|
+
* a monthly-reset decision. It must be storable and cannot be negative.
|
|
558
|
+
*/
|
|
559
|
+
declare function assertValidBalanceTarget(value: unknown, field: string, context?: Record<string, unknown>): asserts value is number;
|
|
560
|
+
/**
|
|
561
|
+
* Validate how a tier write changes the balance. `balance` sets a value the
|
|
562
|
+
* caller computed and `balanceCap` lowers the stored value, so naming both has
|
|
563
|
+
* no single meaning and is refused.
|
|
564
|
+
*/
|
|
565
|
+
declare function assertValidTierBalanceInput(input: {
|
|
566
|
+
balance?: number;
|
|
567
|
+
balanceCap?: number;
|
|
568
|
+
}, context?: Record<string, unknown>): void;
|
|
482
569
|
/**
|
|
483
570
|
* Validate a limit derived from tier configuration.
|
|
484
571
|
*
|
|
@@ -694,4 +781,4 @@ declare function releaseReservationWithJournal(repository: ICreditRepository, us
|
|
|
694
781
|
*/
|
|
695
782
|
declare function reserveCreditsForOperation(repository: ICreditRepository, userId: string, amount: number, operationType: string, expiryMs?: number, idempotencyKey?: string): Promise<PortableReservation>;
|
|
696
783
|
|
|
697
|
-
export { CREDIT_AMOUNT_MAX, CREDIT_AMOUNT_SCALE, CreditError, CreditErrorCode, type CreditErrorCodeType, CreditSource, JournalReferenceType, type PaymentEventPayload, PortableReservation, PortableTransaction, RESERVATION_STATUSES, RESERVED_JOURNAL_KEY_PREFIX, type ReservationIntegrityView, ReservationStatus, type StoredPaymentEvent, TERMINAL_RESERVATION_STATUSES, TerminalReservationStatus, 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 };
|
|
784
|
+
export { CREDIT_AMOUNT_MAX, CREDIT_AMOUNT_SCALE, CreditError, CreditErrorCode, type CreditErrorCodeType, CreditSource, JournalReferenceType, type PaymentEventPayload, PortableReservation, PortableTransaction, RESERVATION_STATUSES, RESERVED_JOURNAL_KEY_PREFIX, type ReservationIntegrityView, ReservationStatus, type StoredPaymentEvent, TERMINAL_RESERVATION_STATUSES, TerminalReservationStatus, 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 };
|
package/dist/core/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { R as ReservationStatus, a as PortableReservation, P as PortableTransaction, b as CreditSource, J as JournalReferenceType } from '../types-
|
|
2
|
-
export { A as AIProviderType, B as BuiltinTier, g as CreditCheckResult, C as CreditOperationType, D as DeductCreditsResult, M as MonthlyResetResult, f as PortableJournalEntry, d as PortableUsageLog, c as PortableUserCredits, h as ResourceType, e as SubscriptionExpiryResult, S as SubscriptionTier, T as TierConfig, i as TransactionType, U as UsageHistoryEntry, j as UsageHistoryResponse, W as WithCreditsOptions, k as calculateAvailableCredits, t as toDate, l as toPortableTimestamp } from '../types-
|
|
3
|
-
import { T as TerminalReservationStatus, I as ICreditRepository } from '../types-
|
|
4
|
-
export { a as AddCreditsOutcome, C as CommitOutcome, E as ExpireOutcome, R as ReleaseOutcome,
|
|
1
|
+
import { R as ReservationStatus, a as PortableReservation, P as PortableTransaction, b as CreditSource, J as JournalReferenceType } from '../types-MaViK5tD.js';
|
|
2
|
+
export { A as AIProviderType, B as BuiltinTier, g as CreditCheckResult, C as CreditOperationType, D as DeductCreditsResult, M as MonthlyResetResult, f as PortableJournalEntry, d as PortableUsageLog, c as PortableUserCredits, h as ResourceType, e as SubscriptionExpiryResult, S as SubscriptionTier, T as TierConfig, i as TransactionType, U as UsageHistoryEntry, j as UsageHistoryResponse, W as WithCreditsOptions, k as calculateAvailableCredits, t as toDate, l as toPortableTimestamp } from '../types-MaViK5tD.js';
|
|
3
|
+
import { T as TerminalReservationStatus, I as ICreditRepository } from '../types-3c-IAwZI.js';
|
|
4
|
+
export { a as AddCreditsOutcome, C as CommitOutcome, E as ExpireOutcome, R as ReleaseOutcome, o as ReserveOutcome, q as isCreditedOutcome, r as isReservedOutcome, s as isWinningOutcome } from '../types-3c-IAwZI.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
|
|
|
7
7
|
/**
|
|
@@ -393,6 +393,7 @@ declare function createPaymentRefConflictError(paymentRef: string, details: {
|
|
|
393
393
|
* These live in core rather than in an adapter so every repository — shipped
|
|
394
394
|
* or third-party — validates identically.
|
|
395
395
|
*/
|
|
396
|
+
|
|
396
397
|
/** `numeric(12, 2)`: 12 significant digits, 2 of them after the point. */
|
|
397
398
|
declare const CREDIT_AMOUNT_SCALE = 2;
|
|
398
399
|
/** Largest representable amount: ten integer digits plus two decimals. */
|
|
@@ -479,6 +480,92 @@ declare function assertRepresentableFields(fields: Record<string, unknown>, cont
|
|
|
479
480
|
* fund commits, so they count toward the backing.
|
|
480
481
|
*/
|
|
481
482
|
declare function backedBalanceFloor(reserved: number, bonusCredits: number): number;
|
|
483
|
+
/**
|
|
484
|
+
* The balance left by a cap: lowered to `cap` at most, never raised, and never
|
|
485
|
+
* cut through the credits backing outstanding holds (see
|
|
486
|
+
* {@link backedBalanceFloor}). A balance already under `cap` is left alone,
|
|
487
|
+
* which is the point of computing this from the stored row inside a transaction
|
|
488
|
+
* rather than writing a value the caller read earlier.
|
|
489
|
+
*/
|
|
490
|
+
declare function cappedBalance(balance: number, cap: number, reserved: number, bonusCredits: number): number;
|
|
491
|
+
/** A balance a lowering write lands on, and how much of it only backs holds. */
|
|
492
|
+
interface PlannedBalance {
|
|
493
|
+
balance: number;
|
|
494
|
+
expiredHoldBacking: number;
|
|
495
|
+
}
|
|
496
|
+
/** The part of a balance not kept only for holds. A missing field reads as 0. */
|
|
497
|
+
declare function spendableBalance(balance: number, expiredHoldBacking?: number): number;
|
|
498
|
+
/**
|
|
499
|
+
* An absolute balance write: `target`, floored at what backs outstanding holds.
|
|
500
|
+
* Whatever the floor kept above `target` is not the account's to spend.
|
|
501
|
+
*/
|
|
502
|
+
declare function planBalanceTarget(target: number, reserved: number, bonusCredits: number): PlannedBalance;
|
|
503
|
+
/**
|
|
504
|
+
* A cap on the spendable part of a balance. Like {@link cappedBalance} it never
|
|
505
|
+
* raises the balance and never cuts through what backs holds, and it records the
|
|
506
|
+
* part kept above the cap so a later release cannot hand it back. Credits already
|
|
507
|
+
* marked stay marked, so applying a cap twice changes nothing.
|
|
508
|
+
*/
|
|
509
|
+
declare function planBalanceCap(balance: number, cap: number, reserved: number, bonusCredits: number, expiredHoldBacking?: number): PlannedBalance;
|
|
510
|
+
/**
|
|
511
|
+
* What a closing hold takes out of the credits kept only for holds: its own share
|
|
512
|
+
* and nothing of another hold's. `holdBacking` comes from
|
|
513
|
+
* {@link attributeExpiredBacking}.
|
|
514
|
+
*/
|
|
515
|
+
declare function settleExpiredHoldBacking(balance: number, expiredHoldBacking: number, holdBacking: number): {
|
|
516
|
+
used: number;
|
|
517
|
+
remaining: number;
|
|
518
|
+
};
|
|
519
|
+
/** The balances a commit lands on, and whether the account could pay it. */
|
|
520
|
+
interface PlannedHoldCommit {
|
|
521
|
+
covered: boolean;
|
|
522
|
+
balance: number;
|
|
523
|
+
bonusCredits: number;
|
|
524
|
+
expiredHoldBacking: number;
|
|
525
|
+
}
|
|
526
|
+
/**
|
|
527
|
+
* Pay a hold: first its own share of the marked credits, then spendable balance,
|
|
528
|
+
* then bonus. Marked credits backing other holds are never touched, so what this
|
|
529
|
+
* commit costs does not depend on which holds closed before it.
|
|
530
|
+
*/
|
|
531
|
+
declare function planHoldCommit(balance: number, bonusCredits: number, expiredHoldBacking: number, holdBacking: number, amount: number): PlannedHoldCommit;
|
|
532
|
+
/** The fields of an open hold that decide its share of the marked credits. */
|
|
533
|
+
type ExpiredBackingHold = Pick<PortableReservation, "id" | "amount" | "expiredBacking" | "holdPlacedAt">;
|
|
534
|
+
/**
|
|
535
|
+
* Split `total` marked credits across open holds, returning each hold's share.
|
|
536
|
+
*
|
|
537
|
+
* Shares a hold already has are kept first, oldest hold first, so lowering `total`
|
|
538
|
+
* trims the newest and raising it never moves credits off a hold. Only what is left
|
|
539
|
+
* goes to holds with room, again oldest first, which makes the split a function of
|
|
540
|
+
* the holds and not of the order they later close in. A hold never carries more than
|
|
541
|
+
* its amount. A row without `holdPlacedAt` is a record, not a hold, and gets nothing.
|
|
542
|
+
*
|
|
543
|
+
* The account's `expiredHoldBacking` is written as the sum of these shares, in the
|
|
544
|
+
* same write, so what `total` could not attribute is not marked. Such credits stay out
|
|
545
|
+
* of reach anyway, because the `reserved` that kept them still counts against them.
|
|
546
|
+
*/
|
|
547
|
+
declare function attributeExpiredBacking(holds: readonly ExpiredBackingHold[], total: number): Map<string, number>;
|
|
548
|
+
/**
|
|
549
|
+
* The field to write for a new backing value: nothing when it was and stays 0, so
|
|
550
|
+
* an account that never had any keeps a document without the field.
|
|
551
|
+
*/
|
|
552
|
+
declare function expiredHoldBackingPatch(next: number, stored?: number): {
|
|
553
|
+
expiredHoldBacking?: number;
|
|
554
|
+
};
|
|
555
|
+
/**
|
|
556
|
+
* Validate a balance target supplied by the application: a cap or the result of
|
|
557
|
+
* a monthly-reset decision. It must be storable and cannot be negative.
|
|
558
|
+
*/
|
|
559
|
+
declare function assertValidBalanceTarget(value: unknown, field: string, context?: Record<string, unknown>): asserts value is number;
|
|
560
|
+
/**
|
|
561
|
+
* Validate how a tier write changes the balance. `balance` sets a value the
|
|
562
|
+
* caller computed and `balanceCap` lowers the stored value, so naming both has
|
|
563
|
+
* no single meaning and is refused.
|
|
564
|
+
*/
|
|
565
|
+
declare function assertValidTierBalanceInput(input: {
|
|
566
|
+
balance?: number;
|
|
567
|
+
balanceCap?: number;
|
|
568
|
+
}, context?: Record<string, unknown>): void;
|
|
482
569
|
/**
|
|
483
570
|
* Validate a limit derived from tier configuration.
|
|
484
571
|
*
|
|
@@ -694,4 +781,4 @@ declare function releaseReservationWithJournal(repository: ICreditRepository, us
|
|
|
694
781
|
*/
|
|
695
782
|
declare function reserveCreditsForOperation(repository: ICreditRepository, userId: string, amount: number, operationType: string, expiryMs?: number, idempotencyKey?: string): Promise<PortableReservation>;
|
|
696
783
|
|
|
697
|
-
export { CREDIT_AMOUNT_MAX, CREDIT_AMOUNT_SCALE, CreditError, CreditErrorCode, type CreditErrorCodeType, CreditSource, JournalReferenceType, type PaymentEventPayload, PortableReservation, PortableTransaction, RESERVATION_STATUSES, RESERVED_JOURNAL_KEY_PREFIX, type ReservationIntegrityView, ReservationStatus, type StoredPaymentEvent, TERMINAL_RESERVATION_STATUSES, TerminalReservationStatus, 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 };
|
|
784
|
+
export { CREDIT_AMOUNT_MAX, CREDIT_AMOUNT_SCALE, CreditError, CreditErrorCode, type CreditErrorCodeType, CreditSource, JournalReferenceType, type PaymentEventPayload, PortableReservation, PortableTransaction, RESERVATION_STATUSES, RESERVED_JOURNAL_KEY_PREFIX, type ReservationIntegrityView, ReservationStatus, type StoredPaymentEvent, TERMINAL_RESERVATION_STATUSES, TerminalReservationStatus, 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 };
|
package/dist/core/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);
|
|
@@ -621,9 +701,9 @@ var creditSystemConfigSchema = z.object({
|
|
|
621
701
|
*/
|
|
622
702
|
reservationExpiryMs: z.number().positive().default(5 * 60 * 1e3),
|
|
623
703
|
/**
|
|
624
|
-
* Default credits for new users (free tier)
|
|
704
|
+
* Default credits for new users (free tier). 0 means no signup grant.
|
|
625
705
|
*/
|
|
626
|
-
defaultFreeCredits: z.number().
|
|
706
|
+
defaultFreeCredits: z.number().min(0).default(25),
|
|
627
707
|
/**
|
|
628
708
|
* Grace period in days after subscription expires before downgrade
|
|
629
709
|
*/
|
|
@@ -1006,12 +1086,16 @@ export {
|
|
|
1006
1086
|
assertRepresentableTierAmount,
|
|
1007
1087
|
assertUnkeyedDirectReservation,
|
|
1008
1088
|
assertUnreferencedDirectTransaction,
|
|
1089
|
+
assertValidBalanceTarget,
|
|
1009
1090
|
assertValidCreditAmount,
|
|
1010
1091
|
assertValidIdempotencyKey,
|
|
1011
1092
|
assertValidStoredAmount,
|
|
1012
1093
|
assertValidStoredAmountRaw,
|
|
1094
|
+
assertValidTierBalanceInput,
|
|
1095
|
+
attributeExpiredBacking,
|
|
1013
1096
|
backedBalanceFloor,
|
|
1014
1097
|
calculateAvailableCredits,
|
|
1098
|
+
cappedBalance,
|
|
1015
1099
|
classifyDatabaseError,
|
|
1016
1100
|
commitReservationWithJournal,
|
|
1017
1101
|
createDeferredExecutor,
|
|
@@ -1027,6 +1111,7 @@ export {
|
|
|
1027
1111
|
createUnsupportedOperationError,
|
|
1028
1112
|
createUserNotFoundError,
|
|
1029
1113
|
describePaymentMismatch,
|
|
1114
|
+
expiredHoldBackingPatch,
|
|
1030
1115
|
genericDeferred,
|
|
1031
1116
|
getSqlState,
|
|
1032
1117
|
hasPlacedHold,
|
|
@@ -1048,9 +1133,14 @@ export {
|
|
|
1048
1133
|
noopDeferred,
|
|
1049
1134
|
normalizePaymentRef,
|
|
1050
1135
|
numericToCents,
|
|
1136
|
+
planBalanceCap,
|
|
1137
|
+
planBalanceTarget,
|
|
1138
|
+
planHoldCommit,
|
|
1051
1139
|
releaseReservationWithJournal,
|
|
1052
1140
|
reserveCreditsForOperation,
|
|
1053
1141
|
sameAmount,
|
|
1142
|
+
settleExpiredHoldBacking,
|
|
1143
|
+
spendableBalance,
|
|
1054
1144
|
storedMonthlyLimit,
|
|
1055
1145
|
sumAmounts,
|
|
1056
1146
|
synchronousDeferred,
|