@nehorai/credits 2.2.0 → 3.1.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.
Files changed (43) hide show
  1. package/dist/adapters/index.cjs +169 -155
  2. package/dist/adapters/index.cjs.map +1 -1
  3. package/dist/adapters/index.d.cts +2 -2
  4. package/dist/adapters/index.d.ts +2 -2
  5. package/dist/adapters/index.js +169 -155
  6. package/dist/adapters/index.js.map +1 -1
  7. package/dist/core/index.cjs +148 -24
  8. package/dist/core/index.cjs.map +1 -1
  9. package/dist/core/index.d.cts +113 -22
  10. package/dist/core/index.d.ts +113 -22
  11. package/dist/core/index.js +137 -22
  12. package/dist/core/index.js.map +1 -1
  13. package/dist/index-BrogG0Gc.d.cts +277 -0
  14. package/dist/index-DOvJES2m.d.ts +277 -0
  15. package/dist/index.cjs +324 -71
  16. package/dist/index.cjs.map +1 -1
  17. package/dist/index.d.cts +5 -5
  18. package/dist/index.d.ts +5 -5
  19. package/dist/index.js +311 -69
  20. package/dist/index.js.map +1 -1
  21. package/dist/repository/index.cjs +254 -49
  22. package/dist/repository/index.cjs.map +1 -1
  23. package/dist/repository/index.d.cts +3 -254
  24. package/dist/repository/index.d.ts +3 -254
  25. package/dist/repository/index.js +254 -49
  26. package/dist/repository/index.js.map +1 -1
  27. package/dist/sdk/index.cjs +1 -0
  28. package/dist/sdk/index.cjs.map +1 -1
  29. package/dist/sdk/index.d.cts +2 -2
  30. package/dist/sdk/index.d.ts +2 -2
  31. package/dist/sdk/index.js +1 -0
  32. package/dist/sdk/index.js.map +1 -1
  33. package/dist/service/index.cjs +42 -19
  34. package/dist/service/index.cjs.map +1 -1
  35. package/dist/service/index.d.cts +2 -2
  36. package/dist/service/index.d.ts +2 -2
  37. package/dist/service/index.js +42 -19
  38. package/dist/service/index.js.map +1 -1
  39. package/dist/{types-3c-IAwZI.d.ts → types-BL3YqxUD.d.ts} +28 -6
  40. package/dist/{types-MaViK5tD.d.cts → types-C3sxDvNb.d.cts} +29 -6
  41. package/dist/{types-MaViK5tD.d.ts → types-C3sxDvNb.d.ts} +29 -6
  42. package/dist/{types-XSjNXO5l.d.cts → types-YLAB8bFk.d.cts} +28 -6
  43. package/package.json +1 -1
@@ -1,5 +1,5 @@
1
- import { c as PortableUserCredits, g as CreditCheckResult, C as CreditOperationType, a as PortableReservation, d as PortableUsageLog, D as DeductCreditsResult, S as SubscriptionTier, j as UsageHistoryResponse, f as PortableJournalEntry } from '../types-MaViK5tD.cjs';
2
- import { I as ICreditRepository, o as ReserveOutcome, m as ReservationTransitionOptions, C as CommitOutcome, R as ReleaseOutcome, A as AddCreditsAtomicOptions, a as AddCreditsOutcome, J as JournalEntryQuery } from '../types-XSjNXO5l.cjs';
1
+ import { c as PortableUserCredits, g as CreditCheckResult, C as CreditOperationType, a as PortableReservation, d as PortableUsageLog, D as DeductCreditsResult, S as SubscriptionTier, j as UsageHistoryResponse, f as PortableJournalEntry } from '../types-C3sxDvNb.cjs';
2
+ import { I as ICreditRepository, o as ReserveOutcome, m as ReservationTransitionOptions, C as CommitOutcome, R as ReleaseOutcome, A as AddCreditsAtomicOptions, a as AddCreditsOutcome, J as JournalEntryQuery } from '../types-YLAB8bFk.cjs';
3
3
 
4
4
  /**
5
5
  * Notification callback type for low balance notifications
@@ -1,5 +1,5 @@
1
- import { c as PortableUserCredits, g as CreditCheckResult, C as CreditOperationType, a as PortableReservation, d as PortableUsageLog, D as DeductCreditsResult, S as SubscriptionTier, j as UsageHistoryResponse, f as PortableJournalEntry } from '../types-MaViK5tD.js';
2
- import { I as ICreditRepository, o as ReserveOutcome, m as ReservationTransitionOptions, C as CommitOutcome, R as ReleaseOutcome, A as AddCreditsAtomicOptions, a as AddCreditsOutcome, J as JournalEntryQuery } from '../types-3c-IAwZI.js';
1
+ import { c as PortableUserCredits, g as CreditCheckResult, C as CreditOperationType, a as PortableReservation, d as PortableUsageLog, D as DeductCreditsResult, S as SubscriptionTier, j as UsageHistoryResponse, f as PortableJournalEntry } from '../types-C3sxDvNb.js';
2
+ import { I as ICreditRepository, o as ReserveOutcome, m as ReservationTransitionOptions, C as CommitOutcome, R as ReleaseOutcome, A as AddCreditsAtomicOptions, a as AddCreditsOutcome, J as JournalEntryQuery } from '../types-BL3YqxUD.js';
3
3
 
4
4
  /**
5
5
  * Notification callback type for low balance notifications
@@ -130,6 +130,7 @@ function floatSum(values) {
130
130
  for (const value of values) total += value;
131
131
  return total;
132
132
  }
133
+ var FLOAT_NOISE_RELATIVE = 64 * Number.EPSILON;
133
134
  function isValidCreditAmount(value) {
134
135
  if (typeof value !== "number") return false;
135
136
  const cents = toCents(value);
@@ -158,6 +159,11 @@ function creditAmountReason(value) {
158
159
  }
159
160
  return `Credit amount ${value} is not exact to ${CREDIT_AMOUNT_SCALE} decimal places`;
160
161
  }
162
+ function isRepresentableAmount(value) {
163
+ if (typeof value !== "number") return false;
164
+ const cents = toCents(value);
165
+ return cents !== null && Math.abs(cents) <= MAX_CENTS;
166
+ }
161
167
  function storedMonthlyLimit(limit) {
162
168
  return Number.isFinite(limit) ? limit : CREDIT_AMOUNT_MAX;
163
169
  }
@@ -335,17 +341,25 @@ var DEFAULT_CONFIG = {
335
341
  }
336
342
  };
337
343
  var currentConfig = DEFAULT_CONFIG;
344
+ function isOperationCostMap(value) {
345
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
346
+ return Object.values(value).every((cost) => isRepresentableAmount(cost) && cost >= 0);
347
+ }
338
348
  function loadConfigFromEnv() {
339
349
  const envConfig = {};
340
350
  const operationCostsEnv = process.env.CREDITS_OPERATION_COSTS;
341
351
  if (operationCostsEnv) {
342
352
  try {
343
353
  const parsed = JSON.parse(operationCostsEnv);
344
- if (typeof parsed === "object" && parsed !== null) {
354
+ if (isOperationCostMap(parsed)) {
345
355
  envConfig.operationCosts = {
346
356
  ...DEFAULT_CONFIG.operationCosts,
347
357
  ...parsed
348
358
  };
359
+ } else {
360
+ console.warn(
361
+ "[Credits Config] Ignoring CREDITS_OPERATION_COSTS: it must be a JSON object whose costs are numbers >= 0, exact to the cent"
362
+ );
349
363
  }
350
364
  } catch {
351
365
  console.warn("[Credits Config] Failed to parse CREDITS_OPERATION_COSTS env var as JSON");
@@ -696,7 +710,8 @@ var CreditsService = class {
696
710
  userId,
697
711
  entryType: "debit",
698
712
  amount: 0,
699
- // No credits deducted, just tier change
713
+ // 0, however far the cap lowered the balance: the totals record the move
714
+ balanceBefore: sumAmounts(data.balance, data.bonusCredits),
700
715
  // The ledger balance, matching every other journal writer: the V2
701
716
  // transitions and add/deduct all record `balance + bonusCredits`, so
702
717
  // recording `balance` alone made the audit trail disagree with itself
@@ -740,6 +755,7 @@ var CreditsService = class {
740
755
  userId,
741
756
  entryType: balanceChange > 0 ? "credit" : "debit",
742
757
  amount: Math.abs(balanceChange),
758
+ balanceBefore: sumAmounts(data.balance, data.bonusCredits),
743
759
  balanceAfter: sumAmounts(
744
760
  resetResult.credits.balance,
745
761
  resetResult.credits.bonusCredits
@@ -974,9 +990,18 @@ var CreditsService = class {
974
990
  */
975
991
  async deductCredits(userId, amount, options) {
976
992
  assertValidCreditAmount(amount, { userId, operation: "deductCredits" });
993
+ const operationType = options?.operationType;
994
+ const journal = {
995
+ referenceId: options?.requestId ?? `deduct-${Date.now()}`,
996
+ description: operationType ? `Deducted ${amount} credits for ${getOperationLabel(operationType)}` : `Deducted ${amount} credits`,
997
+ metadata: {
998
+ ...options?.metadata,
999
+ ...operationType && { operationType }
1000
+ }
1001
+ };
977
1002
  let deduction;
978
1003
  try {
979
- deduction = await this.repository.deductCreditsAtomic(userId, amount);
1004
+ deduction = await this.repository.deductCreditsAtomic(userId, amount, journal);
980
1005
  } catch (error) {
981
1006
  if (isCreditError(error) && error.code === CreditErrorCode.INVALID_AMOUNT) throw error;
982
1007
  const credits = await this.repository.getUserCredits(userId);
@@ -992,21 +1017,18 @@ var CreditsService = class {
992
1017
  shortfall: sumAmounts(amount, -available)
993
1018
  };
994
1019
  }
995
- const operationType = options?.operationType;
996
- await this.repository.createJournalEntry({
997
- userId,
998
- entryType: "debit",
999
- amount,
1000
- balanceAfter: deduction.newBalance,
1001
- source: "operation_commit",
1002
- referenceId: options?.requestId ?? `deduct-${Date.now()}`,
1003
- referenceType: "adjustment",
1004
- description: operationType ? `Deducted ${amount} credits for ${getOperationLabel(operationType)}` : `Deducted ${amount} credits`,
1005
- metadata: {
1006
- ...options?.metadata,
1007
- ...operationType && { operationType }
1008
- }
1009
- });
1020
+ if (deduction.journalEntryId === void 0) {
1021
+ await this.repository.createJournalEntry({
1022
+ userId,
1023
+ entryType: "debit",
1024
+ amount,
1025
+ balanceBefore: deduction.previousBalance,
1026
+ balanceAfter: deduction.newBalance,
1027
+ source: "operation_commit",
1028
+ referenceType: "adjustment",
1029
+ ...journal
1030
+ });
1031
+ }
1010
1032
  if (operationType) {
1011
1033
  await this.logUsage({
1012
1034
  userId,
@@ -1080,7 +1102,8 @@ var CreditsService = class {
1080
1102
  ]);
1081
1103
  const historyEntries = entries.map((entry) => {
1082
1104
  const type = this.mapSourceToHistoryType(entry.source);
1083
- const creditsChange = entry.entryType === "credit" ? entry.amount : -entry.amount;
1105
+ const signedAmount = entry.entryType === "credit" ? entry.amount : -entry.amount;
1106
+ const creditsChange = entry.balanceBefore !== void 0 ? sumAmounts(entry.balanceAfter, -entry.balanceBefore) : signedAmount;
1084
1107
  return {
1085
1108
  id: entry.id,
1086
1109
  type,