@nehorai/credits 3.0.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.
package/dist/index.d.cts CHANGED
@@ -1,10 +1,10 @@
1
1
  import { S as SubscriptionTier, T as TierConfig, a as PortableReservation } from './types-C3sxDvNb.cjs';
2
2
  export { A as AIProviderType, B as BuiltinTier, g as CreditCheckResult, C as CreditOperationType, b as CreditSource, D as DeductCreditsResult, E as ExpiredBackingFrontier, 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-C3sxDvNb.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, D as DeductJournalInput, 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-DOjguhEX.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, D as DeductJournalInput, 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-YLAB8bFk.cjs';
4
4
  export { CREDIT_AMOUNT_MAX, CREDIT_AMOUNT_SCALE, CreditError, CreditErrorCode, CreditErrorCodeType, ExpiredBackingAllocation, ExpiredBackingFields, ExpiredBackingHold, ExpiredBackingRewrite, NO_EXPIRED_BACKING_PREFIX, PaymentEventPayload, RESERVATION_STATUSES, RESERVED_JOURNAL_KEY_PREFIX, ReservationIntegrityView, StoredPaymentEvent, TERMINAL_RESERVATION_STATUSES, 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 } 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 { BalanceValidationResult, InMemoryCreditRepository, addCreditsThroughRepository, assertValidBalanceUpdate, commitThroughRepository, createInMemoryCreditRepository, generateId, getNextMonthlyReset, releaseThroughRepository, reserveThroughRepository, validateBalanceUpdate } from './repository/index.cjs';
7
+ export { B as BalanceValidationResult, I as InMemoryCreditRepository, a as addCreditsThroughRepository, b as assertValidBalanceUpdate, c as commitThroughRepository, d as createInMemoryCreditRepository, g as generateId, e as getNextMonthlyReset, r as releaseThroughRepository, f as reserveThroughRepository, s as signupGrantJournalInput, t as tierChangeJournalInput, v as validateBalanceUpdate } from './index-BrogG0Gc.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';
package/dist/index.d.ts CHANGED
@@ -1,10 +1,10 @@
1
1
  import { S as SubscriptionTier, T as TierConfig, a as PortableReservation } from './types-C3sxDvNb.js';
2
2
  export { A as AIProviderType, B as BuiltinTier, g as CreditCheckResult, C as CreditOperationType, b as CreditSource, D as DeductCreditsResult, E as ExpiredBackingFrontier, 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-C3sxDvNb.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, D as DeductJournalInput, 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-CY-oHIMA.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, D as DeductJournalInput, 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-BL3YqxUD.js';
4
4
  export { CREDIT_AMOUNT_MAX, CREDIT_AMOUNT_SCALE, CreditError, CreditErrorCode, CreditErrorCodeType, ExpiredBackingAllocation, ExpiredBackingFields, ExpiredBackingHold, ExpiredBackingRewrite, NO_EXPIRED_BACKING_PREFIX, PaymentEventPayload, RESERVATION_STATUSES, RESERVED_JOURNAL_KEY_PREFIX, ReservationIntegrityView, StoredPaymentEvent, TERMINAL_RESERVATION_STATUSES, 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 } 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 { BalanceValidationResult, InMemoryCreditRepository, addCreditsThroughRepository, assertValidBalanceUpdate, commitThroughRepository, createInMemoryCreditRepository, generateId, getNextMonthlyReset, releaseThroughRepository, reserveThroughRepository, validateBalanceUpdate } from './repository/index.js';
7
+ export { B as BalanceValidationResult, I as InMemoryCreditRepository, a as addCreditsThroughRepository, b as assertValidBalanceUpdate, c as commitThroughRepository, d as createInMemoryCreditRepository, g as generateId, e as getNextMonthlyReset, r as releaseThroughRepository, f as reserveThroughRepository, s as signupGrantJournalInput, t as tierChangeJournalInput, v as validateBalanceUpdate } from './index-DOvJES2m.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';
package/dist/index.js CHANGED
@@ -913,17 +913,25 @@ var DEFAULT_CONFIG = {
913
913
  }
914
914
  };
915
915
  var currentConfig = DEFAULT_CONFIG;
916
+ function isOperationCostMap(value) {
917
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
918
+ return Object.values(value).every((cost) => isRepresentableAmount(cost) && cost >= 0);
919
+ }
916
920
  function loadConfigFromEnv() {
917
921
  const envConfig = {};
918
922
  const operationCostsEnv = process.env.CREDITS_OPERATION_COSTS;
919
923
  if (operationCostsEnv) {
920
924
  try {
921
925
  const parsed = JSON.parse(operationCostsEnv);
922
- if (typeof parsed === "object" && parsed !== null) {
926
+ if (isOperationCostMap(parsed)) {
923
927
  envConfig.operationCosts = {
924
928
  ...DEFAULT_CONFIG.operationCosts,
925
929
  ...parsed
926
930
  };
931
+ } else {
932
+ console.warn(
933
+ "[Credits Config] Ignoring CREDITS_OPERATION_COSTS: it must be a JSON object whose costs are numbers >= 0, exact to the cent"
934
+ );
927
935
  }
928
936
  } catch {
929
937
  console.warn("[Credits Config] Failed to parse CREDITS_OPERATION_COSTS env var as JSON");
@@ -1371,6 +1379,50 @@ function assertValidBalanceUpdate(current, updates) {
1371
1379
  throw new Error(`Invalid balance update: ${result.errors.join("; ")}`);
1372
1380
  }
1373
1381
  }
1382
+ function signupGrantJournalInput(userId, tier, grant) {
1383
+ if (grant <= 0) return null;
1384
+ return {
1385
+ userId,
1386
+ entryType: "credit",
1387
+ amount: grant,
1388
+ balanceBefore: 0,
1389
+ balanceAfter: grant,
1390
+ source: "bonus",
1391
+ referenceId: `signup-${userId}`,
1392
+ referenceType: "adjustment",
1393
+ description: `Signup grant for ${tier} tier.`,
1394
+ metadata: { tier }
1395
+ };
1396
+ }
1397
+ function tierChangeJournalInput(input) {
1398
+ const { userId, previousTier, newTier, previousBalance, newBalance, bonusCredits } = input;
1399
+ const change = sumAmounts(newBalance, -previousBalance);
1400
+ if (change === 0) return null;
1401
+ const raised = change > 0;
1402
+ const amount = raised ? newBalance : 0;
1403
+ const balanceBefore = sumAmounts(previousBalance, bonusCredits);
1404
+ const balanceAfter = sumAmounts(newBalance, bonusCredits);
1405
+ assertRepresentableFields(
1406
+ {
1407
+ "journal amount": amount,
1408
+ "journal balanceBefore": balanceBefore,
1409
+ "journal balanceAfter": balanceAfter
1410
+ },
1411
+ { userId, operation: "updateUserTier" }
1412
+ );
1413
+ return {
1414
+ userId,
1415
+ entryType: raised ? "credit" : "debit",
1416
+ amount,
1417
+ balanceBefore,
1418
+ balanceAfter,
1419
+ source: raised ? "subscription_upgrade" : "subscription_downgrade",
1420
+ referenceId: `tier-${Date.now()}`,
1421
+ referenceType: "subscription",
1422
+ description: `Tier changed from ${previousTier} to ${newTier}.`,
1423
+ metadata: { previousTier, newTier, previousBalance, newBalance }
1424
+ };
1425
+ }
1374
1426
 
1375
1427
  // src/repository/memory/snapshot.ts
1376
1428
  function copyRecord(record) {
@@ -1914,6 +1966,8 @@ var InMemoryCreditRepository = class {
1914
1966
  updatedAt: now
1915
1967
  };
1916
1968
  this.store.users.set(userId, credits);
1969
+ const grantLine = signupGrantJournalInput(userId, tier, initialBalance);
1970
+ if (grantLine) this.recordJournalEntry(grantLine);
1917
1971
  return credits;
1918
1972
  }
1919
1973
  async updateUserCredits(userId, updates) {
@@ -1994,21 +2048,29 @@ var InMemoryCreditRepository = class {
1994
2048
  if (!credits) {
1995
2049
  throw new Error(`User ${userId} not found`);
1996
2050
  }
1997
- if (input.balanceCap !== void 0 && !isFreeTier(credits.tier)) {
1998
- const capped = planBalanceCap(
2051
+ let planned;
2052
+ if (input.balance !== void 0) {
2053
+ planned = planBalanceTarget(input.balance, credits.reserved, credits.bonusCredits);
2054
+ } else if (input.balanceCap !== void 0 && !isFreeTier(credits.tier)) {
2055
+ planned = planBalanceCap(
1999
2056
  credits.balance,
2000
2057
  input.balanceCap,
2001
2058
  credits.reserved,
2002
2059
  credits.bonusCredits,
2003
2060
  credits.expiredHoldBacking
2004
2061
  );
2005
- credits.balance = capped.balance;
2006
- writeExpiredHoldBacking(this.store, credits, capped.expiredHoldBacking);
2007
2062
  }
2063
+ const journalLine = planned ? tierChangeJournalInput({
2064
+ userId,
2065
+ previousTier: credits.tier,
2066
+ newTier: input.tier,
2067
+ previousBalance: credits.balance,
2068
+ newBalance: planned.balance,
2069
+ bonusCredits: credits.bonusCredits
2070
+ }) : null;
2008
2071
  credits.tier = input.tier;
2009
2072
  credits.monthlyLimit = input.monthlyLimit;
2010
- if (input.balance !== void 0) {
2011
- const planned = planBalanceTarget(input.balance, credits.reserved, credits.bonusCredits);
2073
+ if (planned) {
2012
2074
  credits.balance = planned.balance;
2013
2075
  writeExpiredHoldBacking(this.store, credits, planned.expiredHoldBacking);
2014
2076
  }
@@ -2027,6 +2089,7 @@ var InMemoryCreditRepository = class {
2027
2089
  }
2028
2090
  credits.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
2029
2091
  this.store.users.set(userId, credits);
2092
+ if (journalLine) this.recordJournalEntry(journalLine);
2030
2093
  }
2031
2094
  // ==================== Reservations ====================
2032
2095
  async createReservation(input) {
@@ -2648,7 +2711,7 @@ var InMemoryCreditRepository = class {
2648
2711
  userId,
2649
2712
  entryType: "debit",
2650
2713
  amount: 0,
2651
- // No credits deducted, just tier change
2714
+ // 0, however far the cap lowered the balance: the totals record the move
2652
2715
  balanceBefore: sumAmounts(previousBalance, credits.bonusCredits),
2653
2716
  balanceAfter,
2654
2717
  source: "subscription_downgrade",
@@ -2812,7 +2875,7 @@ var CreditsService = class {
2812
2875
  userId,
2813
2876
  entryType: "debit",
2814
2877
  amount: 0,
2815
- // No credits deducted, just tier change
2878
+ // 0, however far the cap lowered the balance: the totals record the move
2816
2879
  balanceBefore: sumAmounts(data.balance, data.bonusCredits),
2817
2880
  // The ledger balance, matching every other journal writer: the V2
2818
2881
  // transitions and add/deduct all record `balance + bonusCredits`, so
@@ -3204,7 +3267,8 @@ var CreditsService = class {
3204
3267
  ]);
3205
3268
  const historyEntries = entries.map((entry) => {
3206
3269
  const type = this.mapSourceToHistoryType(entry.source);
3207
- const creditsChange = entry.entryType === "credit" ? entry.amount : -entry.amount;
3270
+ const signedAmount = entry.entryType === "credit" ? entry.amount : -entry.amount;
3271
+ const creditsChange = entry.balanceBefore !== void 0 ? sumAmounts(entry.balanceAfter, -entry.balanceBefore) : signedAmount;
3208
3272
  return {
3209
3273
  id: entry.id,
3210
3274
  type,
@@ -4276,6 +4340,7 @@ export {
4276
4340
  resetConfig,
4277
4341
  sameAmount,
4278
4342
  settleExpiredHoldBacking,
4343
+ signupGrantJournalInput,
4279
4344
  spendableBalance,
4280
4345
  storedAmountCeil,
4281
4346
  storedAmountFloor,
@@ -4285,6 +4350,7 @@ export {
4285
4350
  supportsIdempotentCredit,
4286
4351
  synchronousDeferred,
4287
4352
  terminalStatusOf,
4353
+ tierChangeJournalInput,
4288
4354
  tierSchema,
4289
4355
  toCents,
4290
4356
  toClientUserCredits,