@nehorai/credits 2.2.0 → 3.0.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/dist/adapters/index.cjs +1 -0
- 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 +1 -0
- package/dist/adapters/index.js.map +1 -1
- package/dist/core/index.cjs +139 -23
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +113 -22
- package/dist/core/index.d.ts +113 -22
- package/dist/core/index.js +128 -21
- package/dist/core/index.js.map +1 -1
- package/dist/index.cjs +246 -61
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.js +235 -59
- package/dist/index.js.map +1 -1
- package/dist/repository/index.cjs +183 -41
- package/dist/repository/index.cjs.map +1 -1
- package/dist/repository/index.d.cts +6 -5
- package/dist/repository/index.d.ts +6 -5
- package/dist/repository/index.js +183 -41
- package/dist/repository/index.js.map +1 -1
- package/dist/sdk/index.cjs +1 -0
- 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 +1 -0
- package/dist/sdk/index.js.map +1 -1
- package/dist/service/index.cjs +25 -16
- 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 +25 -16
- package/dist/service/index.js.map +1 -1
- package/dist/{types-MaViK5tD.d.cts → types-C3sxDvNb.d.cts} +29 -6
- package/dist/{types-MaViK5tD.d.ts → types-C3sxDvNb.d.ts} +29 -6
- package/dist/{types-3c-IAwZI.d.ts → types-CY-oHIMA.d.ts} +19 -3
- package/dist/{types-XSjNXO5l.d.cts → types-DOjguhEX.d.cts} +19 -3
- package/package.json +1 -1
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, o as ReserveOutcome, q as isCreditedOutcome, r as isReservedOutcome, s as isWinningOutcome } from '../types-
|
|
1
|
+
import { R as ReservationStatus, a as PortableReservation, P as PortableTransaction, b as CreditSource, J as JournalReferenceType, E as ExpiredBackingFrontier } from '../types-C3sxDvNb.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-C3sxDvNb.cjs';
|
|
3
|
+
import { T as TerminalReservationStatus, I as ICreditRepository } from '../types-DOjguhEX.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-DOjguhEX.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
|
/**
|
|
@@ -432,6 +432,40 @@ declare function toCents(value: number): number | null;
|
|
|
432
432
|
* derived result is not enough and never was.
|
|
433
433
|
*/
|
|
434
434
|
declare function sumAmounts(...values: number[]): number;
|
|
435
|
+
/**
|
|
436
|
+
* Multiply a price by a whole quantity, in cents.
|
|
437
|
+
*
|
|
438
|
+
* `1.1 * 3` is `3.3000000000000003`, which the amount validators refuse: the price
|
|
439
|
+
* was legal, and the float product blames the caller for a rounding artefact. A
|
|
440
|
+
* fractional quantity has no exact result on the grid and is refused rather than
|
|
441
|
+
* rounded, so the rounding policy stays with whoever prices fractions.
|
|
442
|
+
*/
|
|
443
|
+
declare function multiplyAmount(unit: number, quantity: number): number;
|
|
444
|
+
/**
|
|
445
|
+
* The cent-grid value a float landed next to, or the value unchanged.
|
|
446
|
+
*
|
|
447
|
+
* Documents written by older versions can hold the result of IEEE 754 additions
|
|
448
|
+
* (`FieldValue.increment` stored `0.30000000000000004`), and an application can pass
|
|
449
|
+
* a raw float difference to a ledger record. That is noise, not an amount. A value
|
|
450
|
+
* genuinely off the grid, such as `0.005`, is not noise and comes back as it went in.
|
|
451
|
+
*/
|
|
452
|
+
declare function canonicalAmount(value: number): number;
|
|
453
|
+
/**
|
|
454
|
+
* Read a stored amount the account owns as a cent-grid value, rounding down.
|
|
455
|
+
*
|
|
456
|
+
* A stored `24.999` makes every later sum off-grid, the validators then refuse the
|
|
457
|
+
* commit or release that needs it, and the holds on that account lock. Rounding down
|
|
458
|
+
* unlocks them. A value within {@link canonicalAmount}'s noise of the next cent reads as
|
|
459
|
+
* that cent, because a float cannot tell the two apart, so this can add at most that noise:
|
|
460
|
+
* 1e-6 up to 7e7, and about 1.4e-4 at the maximum. A non-finite value is left for the
|
|
461
|
+
* validators to refuse.
|
|
462
|
+
*/
|
|
463
|
+
declare function storedAmountFloor(value: number): number;
|
|
464
|
+
/**
|
|
465
|
+
* Read a stored amount the account owes as a cent-grid value, rounding up, so what
|
|
466
|
+
* stands against the balance is never understated. See {@link storedAmountFloor}.
|
|
467
|
+
*/
|
|
468
|
+
declare function storedAmountCeil(value: number): number;
|
|
435
469
|
/** Amounts that may be *moved*: strictly positive and on the cent grid. */
|
|
436
470
|
declare function isValidCreditAmount(value: unknown): value is number;
|
|
437
471
|
/**
|
|
@@ -493,6 +527,13 @@ interface PlannedBalance {
|
|
|
493
527
|
balance: number;
|
|
494
528
|
expiredHoldBacking: number;
|
|
495
529
|
}
|
|
530
|
+
/**
|
|
531
|
+
* The expired credits actually held in a balance: `0 <= held <= balance`, whatever
|
|
532
|
+
* is stored. Every read of `expiredHoldBacking` goes through this. Trusting the stored
|
|
533
|
+
* value made a negative one mint credits: `balance - (-10)` read as spendable, and a
|
|
534
|
+
* close that "used" -10 of backing added 10 to the balance.
|
|
535
|
+
*/
|
|
536
|
+
declare function heldExpiredBacking(balance: number, stored?: number): number;
|
|
496
537
|
/** The part of a balance not kept only for holds. A missing field reads as 0. */
|
|
497
538
|
declare function spendableBalance(balance: number, expiredHoldBacking?: number): number;
|
|
498
539
|
/**
|
|
@@ -509,8 +550,7 @@ declare function planBalanceTarget(target: number, reserved: number, bonusCredit
|
|
|
509
550
|
declare function planBalanceCap(balance: number, cap: number, reserved: number, bonusCredits: number, expiredHoldBacking?: number): PlannedBalance;
|
|
510
551
|
/**
|
|
511
552
|
* 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}.
|
|
553
|
+
* and nothing of another hold's. `holdBacking` comes from {@link holdExpiredBacking}.
|
|
514
554
|
*/
|
|
515
555
|
declare function settleExpiredHoldBacking(balance: number, expiredHoldBacking: number, holdBacking: number): {
|
|
516
556
|
used: number;
|
|
@@ -530,28 +570,79 @@ interface PlannedHoldCommit {
|
|
|
530
570
|
*/
|
|
531
571
|
declare function planHoldCommit(balance: number, bonusCredits: number, expiredHoldBacking: number, holdBacking: number, amount: number): PlannedHoldCommit;
|
|
532
572
|
/** The fields of an open hold that decide its share of the marked credits. */
|
|
533
|
-
type ExpiredBackingHold = Pick<PortableReservation, "id" | "amount" | "
|
|
573
|
+
type ExpiredBackingHold = Pick<PortableReservation, "id" | "amount" | "holdPlacedAt" | "expiredBacking">;
|
|
574
|
+
/** A hold whose stored share has to change for a new total. */
|
|
575
|
+
interface ExpiredBackingRewrite {
|
|
576
|
+
id: string;
|
|
577
|
+
expiredBacking: number;
|
|
578
|
+
}
|
|
579
|
+
/** The marked credits on an account and where they end among its open holds. */
|
|
580
|
+
interface ExpiredBackingAllocation {
|
|
581
|
+
total: number;
|
|
582
|
+
frontier: ExpiredBackingFrontier | null;
|
|
583
|
+
/** Stored shares to write with this total. Absent when none changes. */
|
|
584
|
+
rewrites?: ExpiredBackingRewrite[];
|
|
585
|
+
}
|
|
586
|
+
/**
|
|
587
|
+
* A frontier before every hold: no hold shares through it. It records that the account's
|
|
588
|
+
* marks are placed, so the next close does not walk the holds again.
|
|
589
|
+
*/
|
|
590
|
+
declare const NO_EXPIRED_BACKING_PREFIX: ExpiredBackingFrontier;
|
|
534
591
|
/**
|
|
535
|
-
*
|
|
592
|
+
* The share a 2.2.0 hold document recorded for itself: the order of record for that hold.
|
|
593
|
+
* Clocks are not, since the server that placed a hold may lag the one before it. Rounded
|
|
594
|
+
* up, like every amount that stands against the balance, and never above the hold. Zero
|
|
595
|
+
* means no record, which leaves the hold to the frontier.
|
|
596
|
+
*/
|
|
597
|
+
declare function recordedExpiredBacking(hold: ExpiredBackingHold): number;
|
|
598
|
+
/**
|
|
599
|
+
* Place `total` marked credits on the open holds, so that what each hold takes does not
|
|
600
|
+
* depend on the order they close in.
|
|
601
|
+
*
|
|
602
|
+
* Shares a 2.2.0 hold document stored stand as recorded: they need not be a prefix of
|
|
603
|
+
* anything, and they sit on documents that already exist. The rest goes to the other
|
|
604
|
+
* holds oldest first, each up to its amount. That part is a prefix, so one
|
|
605
|
+
* {@link ExpiredBackingFrontier} on the account describes it and no hold document changes
|
|
606
|
+
* however many there are. A new hold carries no share, so no record is ever added.
|
|
536
607
|
*
|
|
537
|
-
*
|
|
538
|
-
*
|
|
539
|
-
*
|
|
540
|
-
*
|
|
541
|
-
* its amount. A row without `holdPlacedAt` is a record, not a hold, and gets nothing.
|
|
608
|
+
* A total below the records trims them newest first, and only when every other hold has
|
|
609
|
+
* nothing, and a total the other holds cannot take fills what the records left free. Those
|
|
610
|
+
* are the only hold writes: `rewrites`, at most one per record, each record being an open
|
|
611
|
+
* hold from before the upgrade.
|
|
542
612
|
*
|
|
543
|
-
*
|
|
544
|
-
*
|
|
545
|
-
*
|
|
613
|
+
* Marks no hold can carry stay marked. They were out of reach before this ran, and
|
|
614
|
+
* dropping them would make them spendable; the next absolute balance write replaces
|
|
615
|
+
* them. A row without `holdPlacedAt` is a record, not a hold, and a row with an unusable
|
|
616
|
+
* amount cannot close, so neither takes a share.
|
|
546
617
|
*/
|
|
547
|
-
declare function
|
|
618
|
+
declare function allocateExpiredBacking(holds: readonly ExpiredBackingHold[], total: number): ExpiredBackingAllocation;
|
|
548
619
|
/**
|
|
549
|
-
*
|
|
550
|
-
*
|
|
620
|
+
* This hold's share of the marked credits: its own record if it has one, otherwise where
|
|
621
|
+
* it falls against the account's frontier. Pass the `rewrites` of an allocation made in
|
|
622
|
+
* this same step, since the document does not hold them yet.
|
|
551
623
|
*/
|
|
552
|
-
declare function
|
|
624
|
+
declare function holdExpiredBacking(hold: ExpiredBackingHold, frontier?: ExpiredBackingFrontier | null, rewrites?: readonly ExpiredBackingRewrite[]): number;
|
|
625
|
+
/**
|
|
626
|
+
* When a new hold counts as placed: `now`, or just after the frontier if this server's
|
|
627
|
+
* clock is behind the server that placed it. A hold that sorted before the frontier
|
|
628
|
+
* would read as fully marked, taking expired credits that belong to older holds.
|
|
629
|
+
*/
|
|
630
|
+
declare function holdPlacedAtAfter(now: string, frontier?: ExpiredBackingFrontier | null): string;
|
|
631
|
+
/** The account fields that hold the marked credits. */
|
|
632
|
+
interface ExpiredBackingFields {
|
|
553
633
|
expiredHoldBacking?: number;
|
|
554
|
-
|
|
634
|
+
expiredBackingFrontier?: ExpiredBackingFrontier | null;
|
|
635
|
+
}
|
|
636
|
+
/**
|
|
637
|
+
* The fields to write for a new marked total: nothing when it was and stays 0, so an
|
|
638
|
+
* account that never had any keeps a document without the fields. A stored value that
|
|
639
|
+
* is not the new one is overwritten, which is what repairs a bad one. A `frontier` left
|
|
640
|
+
* undefined keeps the stored one.
|
|
641
|
+
*/
|
|
642
|
+
declare function expiredBackingPatch(next: {
|
|
643
|
+
total: number;
|
|
644
|
+
frontier?: ExpiredBackingFrontier | null;
|
|
645
|
+
}, stored: ExpiredBackingFields): ExpiredBackingFields;
|
|
555
646
|
/**
|
|
556
647
|
* Validate a balance target supplied by the application: a cap or the result of
|
|
557
648
|
* a monthly-reset decision. It must be storable and cannot be negative.
|
|
@@ -781,4 +872,4 @@ declare function releaseReservationWithJournal(repository: ICreditRepository, us
|
|
|
781
872
|
*/
|
|
782
873
|
declare function reserveCreditsForOperation(repository: ICreditRepository, userId: string, amount: number, operationType: string, expiryMs?: number, idempotencyKey?: string): Promise<PortableReservation>;
|
|
783
874
|
|
|
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,
|
|
875
|
+
export { CREDIT_AMOUNT_MAX, CREDIT_AMOUNT_SCALE, CreditError, CreditErrorCode, type CreditErrorCodeType, CreditSource, type ExpiredBackingAllocation, type ExpiredBackingFields, ExpiredBackingFrontier, type ExpiredBackingHold, type ExpiredBackingRewrite, JournalReferenceType, NO_EXPIRED_BACKING_PREFIX, type PaymentEventPayload, PortableReservation, PortableTransaction, RESERVATION_STATUSES, RESERVED_JOURNAL_KEY_PREFIX, type ReservationIntegrityView, ReservationStatus, type StoredPaymentEvent, TERMINAL_RESERVATION_STATUSES, TerminalReservationStatus, allocateExpiredBacking, assertDirectStatusWriteAllowed, assertHoldPlaced, assertKnownReservationStatus, assertPublicJournalKey, assertRepresentableAmount, assertRepresentableFields, assertRepresentableTierAmount, assertUnkeyedDirectReservation, assertUnreferencedDirectTransaction, assertValidBalanceTarget, assertValidCreditAmount, assertValidIdempotencyKey, assertValidStoredAmount, assertValidStoredAmountRaw, assertValidTierBalanceInput, backedBalanceFloor, canonicalAmount, cappedBalance, classifyDatabaseError, commitReservationWithJournal, createIdempotencyConflictError, createInsufficientCreditsError, createInvalidAmountError, createInvalidOperationTypeError, createPaymentRefConflictError, createReservationAlreadyProcessedError, createReservationExpiredError, createReservationNotFoundError, createTransientError, createUnsupportedOperationError, createUserNotFoundError, describePaymentMismatch, expiredBackingPatch, getSqlState, hasPlacedHold, heldExpiredBacking, holdExpiredBacking, holdPlacedAtAfter, isCreditError, isIdempotencyConflictError, isInsufficientCreditsError, isRepresentableAmount, isReservationStatus, isReservedJournalKey, isTerminalReservationStatus, isTransientDatabaseError, isTransientError, isUniqueViolation, isValidCreditAmount, isValidIdempotencyKey, multiplyAmount, normalizePaymentRef, numericToCents, planBalanceCap, planBalanceTarget, planHoldCommit, recordedExpiredBacking, releaseReservationWithJournal, reserveCreditsForOperation, sameAmount, settleExpiredHoldBacking, spendableBalance, storedAmountCeil, storedAmountFloor, 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, o as ReserveOutcome, q as isCreditedOutcome, r as isReservedOutcome, s as isWinningOutcome } from '../types-
|
|
1
|
+
import { R as ReservationStatus, a as PortableReservation, P as PortableTransaction, b as CreditSource, J as JournalReferenceType, E as ExpiredBackingFrontier } from '../types-C3sxDvNb.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-C3sxDvNb.js';
|
|
3
|
+
import { T as TerminalReservationStatus, I as ICreditRepository } from '../types-CY-oHIMA.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-CY-oHIMA.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
|
/**
|
|
@@ -432,6 +432,40 @@ declare function toCents(value: number): number | null;
|
|
|
432
432
|
* derived result is not enough and never was.
|
|
433
433
|
*/
|
|
434
434
|
declare function sumAmounts(...values: number[]): number;
|
|
435
|
+
/**
|
|
436
|
+
* Multiply a price by a whole quantity, in cents.
|
|
437
|
+
*
|
|
438
|
+
* `1.1 * 3` is `3.3000000000000003`, which the amount validators refuse: the price
|
|
439
|
+
* was legal, and the float product blames the caller for a rounding artefact. A
|
|
440
|
+
* fractional quantity has no exact result on the grid and is refused rather than
|
|
441
|
+
* rounded, so the rounding policy stays with whoever prices fractions.
|
|
442
|
+
*/
|
|
443
|
+
declare function multiplyAmount(unit: number, quantity: number): number;
|
|
444
|
+
/**
|
|
445
|
+
* The cent-grid value a float landed next to, or the value unchanged.
|
|
446
|
+
*
|
|
447
|
+
* Documents written by older versions can hold the result of IEEE 754 additions
|
|
448
|
+
* (`FieldValue.increment` stored `0.30000000000000004`), and an application can pass
|
|
449
|
+
* a raw float difference to a ledger record. That is noise, not an amount. A value
|
|
450
|
+
* genuinely off the grid, such as `0.005`, is not noise and comes back as it went in.
|
|
451
|
+
*/
|
|
452
|
+
declare function canonicalAmount(value: number): number;
|
|
453
|
+
/**
|
|
454
|
+
* Read a stored amount the account owns as a cent-grid value, rounding down.
|
|
455
|
+
*
|
|
456
|
+
* A stored `24.999` makes every later sum off-grid, the validators then refuse the
|
|
457
|
+
* commit or release that needs it, and the holds on that account lock. Rounding down
|
|
458
|
+
* unlocks them. A value within {@link canonicalAmount}'s noise of the next cent reads as
|
|
459
|
+
* that cent, because a float cannot tell the two apart, so this can add at most that noise:
|
|
460
|
+
* 1e-6 up to 7e7, and about 1.4e-4 at the maximum. A non-finite value is left for the
|
|
461
|
+
* validators to refuse.
|
|
462
|
+
*/
|
|
463
|
+
declare function storedAmountFloor(value: number): number;
|
|
464
|
+
/**
|
|
465
|
+
* Read a stored amount the account owes as a cent-grid value, rounding up, so what
|
|
466
|
+
* stands against the balance is never understated. See {@link storedAmountFloor}.
|
|
467
|
+
*/
|
|
468
|
+
declare function storedAmountCeil(value: number): number;
|
|
435
469
|
/** Amounts that may be *moved*: strictly positive and on the cent grid. */
|
|
436
470
|
declare function isValidCreditAmount(value: unknown): value is number;
|
|
437
471
|
/**
|
|
@@ -493,6 +527,13 @@ interface PlannedBalance {
|
|
|
493
527
|
balance: number;
|
|
494
528
|
expiredHoldBacking: number;
|
|
495
529
|
}
|
|
530
|
+
/**
|
|
531
|
+
* The expired credits actually held in a balance: `0 <= held <= balance`, whatever
|
|
532
|
+
* is stored. Every read of `expiredHoldBacking` goes through this. Trusting the stored
|
|
533
|
+
* value made a negative one mint credits: `balance - (-10)` read as spendable, and a
|
|
534
|
+
* close that "used" -10 of backing added 10 to the balance.
|
|
535
|
+
*/
|
|
536
|
+
declare function heldExpiredBacking(balance: number, stored?: number): number;
|
|
496
537
|
/** The part of a balance not kept only for holds. A missing field reads as 0. */
|
|
497
538
|
declare function spendableBalance(balance: number, expiredHoldBacking?: number): number;
|
|
498
539
|
/**
|
|
@@ -509,8 +550,7 @@ declare function planBalanceTarget(target: number, reserved: number, bonusCredit
|
|
|
509
550
|
declare function planBalanceCap(balance: number, cap: number, reserved: number, bonusCredits: number, expiredHoldBacking?: number): PlannedBalance;
|
|
510
551
|
/**
|
|
511
552
|
* 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}.
|
|
553
|
+
* and nothing of another hold's. `holdBacking` comes from {@link holdExpiredBacking}.
|
|
514
554
|
*/
|
|
515
555
|
declare function settleExpiredHoldBacking(balance: number, expiredHoldBacking: number, holdBacking: number): {
|
|
516
556
|
used: number;
|
|
@@ -530,28 +570,79 @@ interface PlannedHoldCommit {
|
|
|
530
570
|
*/
|
|
531
571
|
declare function planHoldCommit(balance: number, bonusCredits: number, expiredHoldBacking: number, holdBacking: number, amount: number): PlannedHoldCommit;
|
|
532
572
|
/** The fields of an open hold that decide its share of the marked credits. */
|
|
533
|
-
type ExpiredBackingHold = Pick<PortableReservation, "id" | "amount" | "
|
|
573
|
+
type ExpiredBackingHold = Pick<PortableReservation, "id" | "amount" | "holdPlacedAt" | "expiredBacking">;
|
|
574
|
+
/** A hold whose stored share has to change for a new total. */
|
|
575
|
+
interface ExpiredBackingRewrite {
|
|
576
|
+
id: string;
|
|
577
|
+
expiredBacking: number;
|
|
578
|
+
}
|
|
579
|
+
/** The marked credits on an account and where they end among its open holds. */
|
|
580
|
+
interface ExpiredBackingAllocation {
|
|
581
|
+
total: number;
|
|
582
|
+
frontier: ExpiredBackingFrontier | null;
|
|
583
|
+
/** Stored shares to write with this total. Absent when none changes. */
|
|
584
|
+
rewrites?: ExpiredBackingRewrite[];
|
|
585
|
+
}
|
|
586
|
+
/**
|
|
587
|
+
* A frontier before every hold: no hold shares through it. It records that the account's
|
|
588
|
+
* marks are placed, so the next close does not walk the holds again.
|
|
589
|
+
*/
|
|
590
|
+
declare const NO_EXPIRED_BACKING_PREFIX: ExpiredBackingFrontier;
|
|
534
591
|
/**
|
|
535
|
-
*
|
|
592
|
+
* The share a 2.2.0 hold document recorded for itself: the order of record for that hold.
|
|
593
|
+
* Clocks are not, since the server that placed a hold may lag the one before it. Rounded
|
|
594
|
+
* up, like every amount that stands against the balance, and never above the hold. Zero
|
|
595
|
+
* means no record, which leaves the hold to the frontier.
|
|
596
|
+
*/
|
|
597
|
+
declare function recordedExpiredBacking(hold: ExpiredBackingHold): number;
|
|
598
|
+
/**
|
|
599
|
+
* Place `total` marked credits on the open holds, so that what each hold takes does not
|
|
600
|
+
* depend on the order they close in.
|
|
601
|
+
*
|
|
602
|
+
* Shares a 2.2.0 hold document stored stand as recorded: they need not be a prefix of
|
|
603
|
+
* anything, and they sit on documents that already exist. The rest goes to the other
|
|
604
|
+
* holds oldest first, each up to its amount. That part is a prefix, so one
|
|
605
|
+
* {@link ExpiredBackingFrontier} on the account describes it and no hold document changes
|
|
606
|
+
* however many there are. A new hold carries no share, so no record is ever added.
|
|
536
607
|
*
|
|
537
|
-
*
|
|
538
|
-
*
|
|
539
|
-
*
|
|
540
|
-
*
|
|
541
|
-
* its amount. A row without `holdPlacedAt` is a record, not a hold, and gets nothing.
|
|
608
|
+
* A total below the records trims them newest first, and only when every other hold has
|
|
609
|
+
* nothing, and a total the other holds cannot take fills what the records left free. Those
|
|
610
|
+
* are the only hold writes: `rewrites`, at most one per record, each record being an open
|
|
611
|
+
* hold from before the upgrade.
|
|
542
612
|
*
|
|
543
|
-
*
|
|
544
|
-
*
|
|
545
|
-
*
|
|
613
|
+
* Marks no hold can carry stay marked. They were out of reach before this ran, and
|
|
614
|
+
* dropping them would make them spendable; the next absolute balance write replaces
|
|
615
|
+
* them. A row without `holdPlacedAt` is a record, not a hold, and a row with an unusable
|
|
616
|
+
* amount cannot close, so neither takes a share.
|
|
546
617
|
*/
|
|
547
|
-
declare function
|
|
618
|
+
declare function allocateExpiredBacking(holds: readonly ExpiredBackingHold[], total: number): ExpiredBackingAllocation;
|
|
548
619
|
/**
|
|
549
|
-
*
|
|
550
|
-
*
|
|
620
|
+
* This hold's share of the marked credits: its own record if it has one, otherwise where
|
|
621
|
+
* it falls against the account's frontier. Pass the `rewrites` of an allocation made in
|
|
622
|
+
* this same step, since the document does not hold them yet.
|
|
551
623
|
*/
|
|
552
|
-
declare function
|
|
624
|
+
declare function holdExpiredBacking(hold: ExpiredBackingHold, frontier?: ExpiredBackingFrontier | null, rewrites?: readonly ExpiredBackingRewrite[]): number;
|
|
625
|
+
/**
|
|
626
|
+
* When a new hold counts as placed: `now`, or just after the frontier if this server's
|
|
627
|
+
* clock is behind the server that placed it. A hold that sorted before the frontier
|
|
628
|
+
* would read as fully marked, taking expired credits that belong to older holds.
|
|
629
|
+
*/
|
|
630
|
+
declare function holdPlacedAtAfter(now: string, frontier?: ExpiredBackingFrontier | null): string;
|
|
631
|
+
/** The account fields that hold the marked credits. */
|
|
632
|
+
interface ExpiredBackingFields {
|
|
553
633
|
expiredHoldBacking?: number;
|
|
554
|
-
|
|
634
|
+
expiredBackingFrontier?: ExpiredBackingFrontier | null;
|
|
635
|
+
}
|
|
636
|
+
/**
|
|
637
|
+
* The fields to write for a new marked total: nothing when it was and stays 0, so an
|
|
638
|
+
* account that never had any keeps a document without the fields. A stored value that
|
|
639
|
+
* is not the new one is overwritten, which is what repairs a bad one. A `frontier` left
|
|
640
|
+
* undefined keeps the stored one.
|
|
641
|
+
*/
|
|
642
|
+
declare function expiredBackingPatch(next: {
|
|
643
|
+
total: number;
|
|
644
|
+
frontier?: ExpiredBackingFrontier | null;
|
|
645
|
+
}, stored: ExpiredBackingFields): ExpiredBackingFields;
|
|
555
646
|
/**
|
|
556
647
|
* Validate a balance target supplied by the application: a cap or the result of
|
|
557
648
|
* a monthly-reset decision. It must be storable and cannot be negative.
|
|
@@ -781,4 +872,4 @@ declare function releaseReservationWithJournal(repository: ICreditRepository, us
|
|
|
781
872
|
*/
|
|
782
873
|
declare function reserveCreditsForOperation(repository: ICreditRepository, userId: string, amount: number, operationType: string, expiryMs?: number, idempotencyKey?: string): Promise<PortableReservation>;
|
|
783
874
|
|
|
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,
|
|
875
|
+
export { CREDIT_AMOUNT_MAX, CREDIT_AMOUNT_SCALE, CreditError, CreditErrorCode, type CreditErrorCodeType, CreditSource, type ExpiredBackingAllocation, type ExpiredBackingFields, ExpiredBackingFrontier, type ExpiredBackingHold, type ExpiredBackingRewrite, JournalReferenceType, NO_EXPIRED_BACKING_PREFIX, type PaymentEventPayload, PortableReservation, PortableTransaction, RESERVATION_STATUSES, RESERVED_JOURNAL_KEY_PREFIX, type ReservationIntegrityView, ReservationStatus, type StoredPaymentEvent, TERMINAL_RESERVATION_STATUSES, TerminalReservationStatus, allocateExpiredBacking, assertDirectStatusWriteAllowed, assertHoldPlaced, assertKnownReservationStatus, assertPublicJournalKey, assertRepresentableAmount, assertRepresentableFields, assertRepresentableTierAmount, assertUnkeyedDirectReservation, assertUnreferencedDirectTransaction, assertValidBalanceTarget, assertValidCreditAmount, assertValidIdempotencyKey, assertValidStoredAmount, assertValidStoredAmountRaw, assertValidTierBalanceInput, backedBalanceFloor, canonicalAmount, cappedBalance, classifyDatabaseError, commitReservationWithJournal, createIdempotencyConflictError, createInsufficientCreditsError, createInvalidAmountError, createInvalidOperationTypeError, createPaymentRefConflictError, createReservationAlreadyProcessedError, createReservationExpiredError, createReservationNotFoundError, createTransientError, createUnsupportedOperationError, createUserNotFoundError, describePaymentMismatch, expiredBackingPatch, getSqlState, hasPlacedHold, heldExpiredBacking, holdExpiredBacking, holdPlacedAtAfter, isCreditError, isIdempotencyConflictError, isInsufficientCreditsError, isRepresentableAmount, isReservationStatus, isReservedJournalKey, isTerminalReservationStatus, isTransientDatabaseError, isTransientError, isUniqueViolation, isValidCreditAmount, isValidIdempotencyKey, multiplyAmount, normalizePaymentRef, numericToCents, planBalanceCap, planBalanceTarget, planHoldCommit, recordedExpiredBacking, releaseReservationWithJournal, reserveCreditsForOperation, sameAmount, settleExpiredHoldBacking, spendableBalance, storedAmountCeil, storedAmountFloor, storedMonthlyLimit, sumAmounts, terminalStatusOf, toCents };
|
package/dist/core/index.js
CHANGED
|
@@ -167,6 +167,40 @@ function floatSum(values) {
|
|
|
167
167
|
for (const value of values) total += value;
|
|
168
168
|
return total;
|
|
169
169
|
}
|
|
170
|
+
function multiplyAmount(unit, quantity) {
|
|
171
|
+
const cents = toCents(unit);
|
|
172
|
+
const product = cents === null ? Number.NaN : cents * quantity;
|
|
173
|
+
if (cents === null || cents < 0 || !Number.isSafeInteger(quantity) || quantity < 0 || !Number.isSafeInteger(product) || product > MAX_CENTS) {
|
|
174
|
+
throw new CreditError(
|
|
175
|
+
`Cannot price ${quantity} units at ${unit}: the price must be a non-negative amount exact to ${CREDIT_AMOUNT_SCALE} decimals, the quantity a whole number, and the result at most ${CREDIT_AMOUNT_MAX}`,
|
|
176
|
+
CreditErrorCode.INVALID_AMOUNT,
|
|
177
|
+
{ unit, quantity, max: CREDIT_AMOUNT_MAX, scale: CREDIT_AMOUNT_SCALE }
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
return product / 100;
|
|
181
|
+
}
|
|
182
|
+
var FLOAT_NOISE_FLOOR = 1e-6;
|
|
183
|
+
var FLOAT_NOISE_RELATIVE = 64 * Number.EPSILON;
|
|
184
|
+
function floatNoise(value) {
|
|
185
|
+
return Math.max(FLOAT_NOISE_FLOOR, Math.abs(value) * FLOAT_NOISE_RELATIVE);
|
|
186
|
+
}
|
|
187
|
+
function canonicalAmount(value) {
|
|
188
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return value;
|
|
189
|
+
const cents = Math.round(value * 100);
|
|
190
|
+
if (!Number.isSafeInteger(cents)) return value;
|
|
191
|
+
const onGrid = cents / 100 + 0;
|
|
192
|
+
return Math.abs(value - onGrid) < floatNoise(value) ? onGrid : value;
|
|
193
|
+
}
|
|
194
|
+
function storedAmountFloor(value) {
|
|
195
|
+
const canonical = canonicalAmount(value);
|
|
196
|
+
if (!Number.isFinite(canonical) || toCents(canonical) !== null) return canonical;
|
|
197
|
+
return Math.floor(canonical * 100) / 100;
|
|
198
|
+
}
|
|
199
|
+
function storedAmountCeil(value) {
|
|
200
|
+
const canonical = canonicalAmount(value);
|
|
201
|
+
if (!Number.isFinite(canonical) || toCents(canonical) !== null) return canonical;
|
|
202
|
+
return Math.ceil(canonical * 100) / 100;
|
|
203
|
+
}
|
|
170
204
|
function isValidCreditAmount(value) {
|
|
171
205
|
if (typeof value !== "number") return false;
|
|
172
206
|
const cents = toCents(value);
|
|
@@ -222,14 +256,19 @@ function backedBalanceFloor(reserved, bonusCredits) {
|
|
|
222
256
|
function cappedBalance(balance, cap, reserved, bonusCredits) {
|
|
223
257
|
return Math.min(balance, Math.max(cap, backedBalanceFloor(reserved, bonusCredits)));
|
|
224
258
|
}
|
|
225
|
-
function
|
|
226
|
-
|
|
259
|
+
function heldExpiredBacking(balance, stored) {
|
|
260
|
+
if (!(stored !== void 0 && stored > 0)) return 0;
|
|
261
|
+
return Math.max(0, Math.min(stored, balance));
|
|
262
|
+
}
|
|
263
|
+
function spendableBalance(balance, expiredHoldBacking) {
|
|
264
|
+
return sumAmounts(balance, -heldExpiredBacking(balance, expiredHoldBacking));
|
|
227
265
|
}
|
|
228
266
|
function planBalanceTarget(target, reserved, bonusCredits) {
|
|
267
|
+
assertValidBalanceTarget(target, "balance target", { reserved, bonusCredits });
|
|
229
268
|
const balance = Math.max(target, backedBalanceFloor(reserved, bonusCredits));
|
|
230
269
|
return { balance, expiredHoldBacking: sumAmounts(balance, -target) };
|
|
231
270
|
}
|
|
232
|
-
function planBalanceCap(balance, cap, reserved, bonusCredits, expiredHoldBacking
|
|
271
|
+
function planBalanceCap(balance, cap, reserved, bonusCredits, expiredHoldBacking) {
|
|
233
272
|
const spendable = spendableBalance(balance, expiredHoldBacking);
|
|
234
273
|
const target = Math.min(spendable, cap);
|
|
235
274
|
const kept = Math.max(
|
|
@@ -240,8 +279,8 @@ function planBalanceCap(balance, cap, reserved, bonusCredits, expiredHoldBacking
|
|
|
240
279
|
return { balance: next, expiredHoldBacking: Math.max(0, sumAmounts(next, -target)) };
|
|
241
280
|
}
|
|
242
281
|
function settleExpiredHoldBacking(balance, expiredHoldBacking, holdBacking) {
|
|
243
|
-
const held =
|
|
244
|
-
const used = Math.min(held, holdBacking);
|
|
282
|
+
const held = heldExpiredBacking(balance, expiredHoldBacking);
|
|
283
|
+
const used = Math.max(0, Math.min(held, holdBacking));
|
|
245
284
|
return { used, remaining: sumAmounts(held, -used) };
|
|
246
285
|
}
|
|
247
286
|
function planHoldCommit(balance, bonusCredits, expiredHoldBacking, holdBacking, amount) {
|
|
@@ -256,27 +295,83 @@ function planHoldCommit(balance, bonusCredits, expiredHoldBacking, holdBacking,
|
|
|
256
295
|
expiredHoldBacking: backing.remaining
|
|
257
296
|
};
|
|
258
297
|
}
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
298
|
+
var NO_EXPIRED_BACKING_PREFIX = {
|
|
299
|
+
holdPlacedAt: "",
|
|
300
|
+
id: "",
|
|
301
|
+
share: 0
|
|
302
|
+
};
|
|
303
|
+
function compareHolds(a, b) {
|
|
304
|
+
if (a.holdPlacedAt !== b.holdPlacedAt) return a.holdPlacedAt < b.holdPlacedAt ? -1 : 1;
|
|
305
|
+
if (a.id === b.id) return 0;
|
|
306
|
+
return a.id < b.id ? -1 : 1;
|
|
307
|
+
}
|
|
308
|
+
function recordedExpiredBacking(hold) {
|
|
309
|
+
const stored = hold.expiredBacking;
|
|
310
|
+
if (typeof stored !== "number" || !Number.isFinite(stored) || stored <= 0) return 0;
|
|
311
|
+
if (!isValidCreditAmount(hold.amount)) return 0;
|
|
312
|
+
return Math.min(storedAmountCeil(stored), hold.amount);
|
|
313
|
+
}
|
|
314
|
+
function allocateExpiredBacking(holds, total) {
|
|
315
|
+
const placed = holds.filter((hold) => hold.holdPlacedAt && isValidCreditAmount(hold.amount)).map((hold) => ({ ...hold, holdPlacedAt: hold.holdPlacedAt })).sort(compareHolds);
|
|
316
|
+
const wanted = Math.max(0, total);
|
|
317
|
+
if (wanted === 0) return { total: 0, frontier: null };
|
|
318
|
+
const recorded = placed.filter((hold) => recordedExpiredBacking(hold) > 0);
|
|
319
|
+
const shares = new Map(recorded.map((hold) => [hold.id, recordedExpiredBacking(hold)]));
|
|
320
|
+
let left = sumAmounts(wanted, ...[...shares.values()].map((share) => -share));
|
|
321
|
+
for (const hold of [...recorded].reverse()) {
|
|
322
|
+
if (left >= 0) break;
|
|
323
|
+
const share = shares.get(hold.id);
|
|
324
|
+
const cut = Math.min(share, -left);
|
|
325
|
+
shares.set(hold.id, sumAmounts(share, -cut));
|
|
326
|
+
left = sumAmounts(left, cut);
|
|
269
327
|
}
|
|
328
|
+
let frontier = NO_EXPIRED_BACKING_PREFIX;
|
|
270
329
|
for (const hold of placed) {
|
|
330
|
+
if (left <= 0) break;
|
|
331
|
+
if (shares.has(hold.id)) continue;
|
|
332
|
+
const share = Math.min(hold.amount, left);
|
|
333
|
+
frontier = { holdPlacedAt: hold.holdPlacedAt, id: hold.id, share };
|
|
334
|
+
left = sumAmounts(left, -share);
|
|
335
|
+
}
|
|
336
|
+
for (const hold of recorded) {
|
|
337
|
+
if (left <= 0) break;
|
|
271
338
|
const share = shares.get(hold.id);
|
|
272
339
|
const added = Math.min(sumAmounts(hold.amount, -share), left);
|
|
273
340
|
shares.set(hold.id, sumAmounts(share, added));
|
|
274
341
|
left = sumAmounts(left, -added);
|
|
275
342
|
}
|
|
276
|
-
|
|
343
|
+
const rewrites = recorded.filter((hold) => shares.get(hold.id) !== hold.expiredBacking).map((hold) => ({ id: hold.id, expiredBacking: shares.get(hold.id) }));
|
|
344
|
+
return { total: wanted, frontier, ...rewrites.length > 0 ? { rewrites } : {} };
|
|
277
345
|
}
|
|
278
|
-
function
|
|
279
|
-
|
|
346
|
+
function holdExpiredBacking(hold, frontier, rewrites) {
|
|
347
|
+
const rewritten = rewrites?.find((rewrite) => rewrite.id === hold.id);
|
|
348
|
+
const recorded = recordedExpiredBacking(
|
|
349
|
+
rewritten ? { ...hold, expiredBacking: rewritten.expiredBacking } : hold
|
|
350
|
+
);
|
|
351
|
+
if (recorded > 0) return recorded;
|
|
352
|
+
if (!frontier || !hold.holdPlacedAt) return 0;
|
|
353
|
+
const order = compareHolds({ holdPlacedAt: hold.holdPlacedAt, id: hold.id }, frontier);
|
|
354
|
+
if (order > 0) return 0;
|
|
355
|
+
return order < 0 ? hold.amount : Math.min(frontier.share, hold.amount);
|
|
356
|
+
}
|
|
357
|
+
function holdPlacedAtAfter(now, frontier) {
|
|
358
|
+
if (!frontier?.holdPlacedAt) return now;
|
|
359
|
+
const earliest = new Date(Date.parse(frontier.holdPlacedAt) + 1).toISOString();
|
|
360
|
+
return now > earliest ? now : earliest;
|
|
361
|
+
}
|
|
362
|
+
function expiredBackingPatch(next, stored) {
|
|
363
|
+
const total = Math.max(0, next.total);
|
|
364
|
+
const patch = {};
|
|
365
|
+
const storedTotal = stored.expiredHoldBacking;
|
|
366
|
+
if (total > 0 || storedTotal !== void 0 && storedTotal !== 0) {
|
|
367
|
+
patch.expiredHoldBacking = total;
|
|
368
|
+
}
|
|
369
|
+
if (total <= 0) {
|
|
370
|
+
if (stored.expiredBackingFrontier) patch.expiredBackingFrontier = null;
|
|
371
|
+
} else if (next.frontier) {
|
|
372
|
+
patch.expiredBackingFrontier = next.frontier;
|
|
373
|
+
}
|
|
374
|
+
return patch;
|
|
280
375
|
}
|
|
281
376
|
function assertValidBalanceTarget(value, field, context) {
|
|
282
377
|
assertRepresentableAmount(value, field, context);
|
|
@@ -289,7 +384,10 @@ function assertValidBalanceTarget(value, field, context) {
|
|
|
289
384
|
}
|
|
290
385
|
}
|
|
291
386
|
function assertValidTierBalanceInput(input, context) {
|
|
292
|
-
if (input.balanceCap === void 0)
|
|
387
|
+
if (input.balanceCap === void 0) {
|
|
388
|
+
if (input.balance !== void 0) assertValidBalanceTarget(input.balance, "balance", context);
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
293
391
|
if (input.balance !== void 0) {
|
|
294
392
|
throw new CreditError(
|
|
295
393
|
"A tier update cannot set both balance and balanceCap",
|
|
@@ -1074,9 +1172,11 @@ export {
|
|
|
1074
1172
|
CREDIT_AMOUNT_SCALE,
|
|
1075
1173
|
CreditError,
|
|
1076
1174
|
CreditErrorCode,
|
|
1175
|
+
NO_EXPIRED_BACKING_PREFIX,
|
|
1077
1176
|
RESERVATION_STATUSES,
|
|
1078
1177
|
RESERVED_JOURNAL_KEY_PREFIX,
|
|
1079
1178
|
TERMINAL_RESERVATION_STATUSES,
|
|
1179
|
+
allocateExpiredBacking,
|
|
1080
1180
|
assertDirectStatusWriteAllowed,
|
|
1081
1181
|
assertHoldPlaced,
|
|
1082
1182
|
assertKnownReservationStatus,
|
|
@@ -1092,9 +1192,9 @@ export {
|
|
|
1092
1192
|
assertValidStoredAmount,
|
|
1093
1193
|
assertValidStoredAmountRaw,
|
|
1094
1194
|
assertValidTierBalanceInput,
|
|
1095
|
-
attributeExpiredBacking,
|
|
1096
1195
|
backedBalanceFloor,
|
|
1097
1196
|
calculateAvailableCredits,
|
|
1197
|
+
canonicalAmount,
|
|
1098
1198
|
cappedBalance,
|
|
1099
1199
|
classifyDatabaseError,
|
|
1100
1200
|
commitReservationWithJournal,
|
|
@@ -1111,10 +1211,13 @@ export {
|
|
|
1111
1211
|
createUnsupportedOperationError,
|
|
1112
1212
|
createUserNotFoundError,
|
|
1113
1213
|
describePaymentMismatch,
|
|
1114
|
-
|
|
1214
|
+
expiredBackingPatch,
|
|
1115
1215
|
genericDeferred,
|
|
1116
1216
|
getSqlState,
|
|
1117
1217
|
hasPlacedHold,
|
|
1218
|
+
heldExpiredBacking,
|
|
1219
|
+
holdExpiredBacking,
|
|
1220
|
+
holdPlacedAtAfter,
|
|
1118
1221
|
isCreditError,
|
|
1119
1222
|
isCreditedOutcome,
|
|
1120
1223
|
isIdempotencyConflictError,
|
|
@@ -1130,17 +1233,21 @@ export {
|
|
|
1130
1233
|
isValidCreditAmount,
|
|
1131
1234
|
isValidIdempotencyKey,
|
|
1132
1235
|
isWinningOutcome,
|
|
1236
|
+
multiplyAmount,
|
|
1133
1237
|
noopDeferred,
|
|
1134
1238
|
normalizePaymentRef,
|
|
1135
1239
|
numericToCents,
|
|
1136
1240
|
planBalanceCap,
|
|
1137
1241
|
planBalanceTarget,
|
|
1138
1242
|
planHoldCommit,
|
|
1243
|
+
recordedExpiredBacking,
|
|
1139
1244
|
releaseReservationWithJournal,
|
|
1140
1245
|
reserveCreditsForOperation,
|
|
1141
1246
|
sameAmount,
|
|
1142
1247
|
settleExpiredHoldBacking,
|
|
1143
1248
|
spendableBalance,
|
|
1249
|
+
storedAmountCeil,
|
|
1250
|
+
storedAmountFloor,
|
|
1144
1251
|
storedMonthlyLimit,
|
|
1145
1252
|
sumAmounts,
|
|
1146
1253
|
synchronousDeferred,
|