@nehorai/credits 1.2.0 → 1.4.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 (139) hide show
  1. package/LICENSE +21 -0
  2. package/dist/adapters/index.cjs +463 -0
  3. package/dist/adapters/index.cjs.map +1 -0
  4. package/dist/adapters/index.d.cts +66 -0
  5. package/dist/adapters/index.d.ts +66 -3
  6. package/dist/adapters/index.js +435 -1
  7. package/dist/adapters/index.js.map +1 -1
  8. package/dist/auth/index.cjs +96 -0
  9. package/dist/auth/index.cjs.map +1 -0
  10. package/dist/auth/{api-key.d.ts → index.d.cts} +9 -6
  11. package/dist/auth/index.d.ts +37 -5
  12. package/dist/auth/index.js +65 -7
  13. package/dist/auth/index.js.map +1 -1
  14. package/dist/core/index.cjs +522 -0
  15. package/dist/core/index.cjs.map +1 -0
  16. package/dist/core/index.d.cts +119 -0
  17. package/dist/core/index.d.ts +115 -25
  18. package/dist/core/index.js +475 -27
  19. package/dist/core/index.js.map +1 -1
  20. package/dist/{core/deferred.d.ts → deferred-C7rrRfkm.d.cts} +7 -6
  21. package/dist/{core/deferred.js → deferred-C7rrRfkm.d.ts} +23 -29
  22. package/dist/index.cjs +2514 -0
  23. package/dist/index.cjs.map +1 -0
  24. package/dist/index.d.cts +437 -0
  25. package/dist/index.d.ts +429 -50
  26. package/dist/index.js +2407 -63
  27. package/dist/index.js.map +1 -1
  28. package/dist/repository/index.cjs +819 -0
  29. package/dist/repository/index.cjs.map +1 -0
  30. package/dist/repository/{memory/index.d.ts → index.d.cts} +35 -5
  31. package/dist/repository/index.d.ts +97 -7
  32. package/dist/repository/index.js +786 -10
  33. package/dist/repository/index.js.map +1 -1
  34. package/dist/sdk/index.cjs +608 -0
  35. package/dist/sdk/index.cjs.map +1 -0
  36. package/dist/sdk/index.d.cts +495 -0
  37. package/dist/sdk/index.d.ts +491 -9
  38. package/dist/sdk/index.js +568 -12
  39. package/dist/sdk/index.js.map +1 -1
  40. package/dist/service/index.cjs +675 -0
  41. package/dist/service/index.cjs.map +1 -0
  42. package/dist/service/{credits-service.d.ts → index.d.cts} +25 -8
  43. package/dist/service/index.d.ts +156 -3
  44. package/dist/service/index.js +646 -1
  45. package/dist/service/index.js.map +1 -1
  46. package/dist/{core/types.d.ts → types-BBxIWHK2.d.cts} +25 -24
  47. package/dist/types-BBxIWHK2.d.ts +277 -0
  48. package/dist/types-D1vdeBcA.d.cts +305 -0
  49. package/dist/{auth/types.d.ts → types-OZU3I5OM.d.cts} +5 -4
  50. package/dist/types-OZU3I5OM.d.ts +32 -0
  51. package/dist/{repository/types.d.ts → types-aJsyv8df.d.ts} +35 -13
  52. package/package.json +21 -19
  53. package/dist/adapters/generic.d.ts +0 -18
  54. package/dist/adapters/generic.d.ts.map +0 -1
  55. package/dist/adapters/generic.js +0 -151
  56. package/dist/adapters/generic.js.map +0 -1
  57. package/dist/adapters/index.d.ts.map +0 -1
  58. package/dist/adapters/types.d.ts +0 -45
  59. package/dist/adapters/types.d.ts.map +0 -1
  60. package/dist/adapters/types.js +0 -8
  61. package/dist/adapters/types.js.map +0 -1
  62. package/dist/auth/api-key.d.ts.map +0 -1
  63. package/dist/auth/api-key.js +0 -94
  64. package/dist/auth/api-key.js.map +0 -1
  65. package/dist/auth/index.d.ts.map +0 -1
  66. package/dist/auth/types.d.ts.map +0 -1
  67. package/dist/auth/types.js +0 -2
  68. package/dist/auth/types.js.map +0 -1
  69. package/dist/config/costs.d.ts +0 -56
  70. package/dist/config/costs.d.ts.map +0 -1
  71. package/dist/config/costs.js +0 -71
  72. package/dist/config/costs.js.map +0 -1
  73. package/dist/config/index.d.ts +0 -184
  74. package/dist/config/index.d.ts.map +0 -1
  75. package/dist/config/index.js +0 -342
  76. package/dist/config/index.js.map +0 -1
  77. package/dist/config/provider.d.ts +0 -12
  78. package/dist/config/provider.d.ts.map +0 -1
  79. package/dist/config/provider.js +0 -14
  80. package/dist/config/provider.js.map +0 -1
  81. package/dist/core/deferred.d.ts.map +0 -1
  82. package/dist/core/deferred.js.map +0 -1
  83. package/dist/core/errors.d.ts +0 -78
  84. package/dist/core/errors.d.ts.map +0 -1
  85. package/dist/core/errors.js +0 -110
  86. package/dist/core/errors.js.map +0 -1
  87. package/dist/core/index.d.ts.map +0 -1
  88. package/dist/core/operations.d.ts +0 -36
  89. package/dist/core/operations.d.ts.map +0 -1
  90. package/dist/core/operations.js +0 -88
  91. package/dist/core/operations.js.map +0 -1
  92. package/dist/core/types.d.ts.map +0 -1
  93. package/dist/core/types.js +0 -82
  94. package/dist/core/types.js.map +0 -1
  95. package/dist/index.d.ts.map +0 -1
  96. package/dist/notifications/index.d.ts +0 -76
  97. package/dist/notifications/index.d.ts.map +0 -1
  98. package/dist/notifications/index.js +0 -174
  99. package/dist/notifications/index.js.map +0 -1
  100. package/dist/repository/index.d.ts.map +0 -1
  101. package/dist/repository/memory/index.d.ts.map +0 -1
  102. package/dist/repository/memory/index.js +0 -518
  103. package/dist/repository/memory/index.js.map +0 -1
  104. package/dist/repository/types.d.ts.map +0 -1
  105. package/dist/repository/types.js +0 -39
  106. package/dist/repository/types.js.map +0 -1
  107. package/dist/repository/utils.d.ts +0 -22
  108. package/dist/repository/utils.d.ts.map +0 -1
  109. package/dist/repository/utils.js +0 -40
  110. package/dist/repository/utils.js.map +0 -1
  111. package/dist/sdk/admin-client.d.ts +0 -146
  112. package/dist/sdk/admin-client.d.ts.map +0 -1
  113. package/dist/sdk/admin-client.js +0 -196
  114. package/dist/sdk/admin-client.js.map +0 -1
  115. package/dist/sdk/client.d.ts +0 -144
  116. package/dist/sdk/client.d.ts.map +0 -1
  117. package/dist/sdk/client.js +0 -247
  118. package/dist/sdk/client.js.map +0 -1
  119. package/dist/sdk/errors.d.ts +0 -91
  120. package/dist/sdk/errors.d.ts.map +0 -1
  121. package/dist/sdk/errors.js +0 -185
  122. package/dist/sdk/errors.js.map +0 -1
  123. package/dist/sdk/index.d.ts.map +0 -1
  124. package/dist/sdk/types.d.ts +0 -110
  125. package/dist/sdk/types.d.ts.map +0 -1
  126. package/dist/sdk/types.js +0 -20
  127. package/dist/sdk/types.js.map +0 -1
  128. package/dist/service/credits-service.d.ts.map +0 -1
  129. package/dist/service/credits-service.js +0 -368
  130. package/dist/service/credits-service.js.map +0 -1
  131. package/dist/service/index.d.ts.map +0 -1
  132. package/dist/utils/error-utils.d.ts +0 -48
  133. package/dist/utils/error-utils.d.ts.map +0 -1
  134. package/dist/utils/error-utils.js +0 -57
  135. package/dist/utils/error-utils.js.map +0 -1
  136. package/dist/utils/index.d.ts +0 -54
  137. package/dist/utils/index.d.ts.map +0 -1
  138. package/dist/utils/index.js +0 -72
  139. package/dist/utils/index.js.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/core/types.ts","../src/core/errors.ts","../src/core/deferred.ts","../src/config/index.ts","../src/config/provider.ts","../src/core/operations.ts","../src/config/costs.ts","../src/repository/types.ts","../src/repository/utils.ts","../src/repository/memory/index.ts","../src/auth/api-key.ts","../src/service/credits-service.ts","../src/adapters/generic.ts","../src/notifications/index.ts","../src/sdk/types.ts","../src/sdk/errors.ts","../src/sdk/client.ts","../src/sdk/admin-client.ts","../src/utils/error-utils.ts","../src/utils/index.ts"],"sourcesContent":["/**\n * @nehorai/credits - Framework-agnostic credits system\n *\n * Provides a complete credits/billing system with:\n * - Two-phase commit for safe credit deduction\n * - Journal-based audit trail\n * - Subscription tier management\n * - Notification hooks\n * - REST API SDK\n *\n * @example\n * ```typescript\n * import {\n * CreditsService,\n * createInMemoryCreditRepository,\n * CreditError,\n * } from '@nehorai/credits';\n *\n * const repository = createInMemoryCreditRepository();\n * const service = new CreditsService(repository);\n *\n * // Check credits\n * const check = await service.checkCredits(userId, 10);\n * if (!check.hasCredits) {\n * throw new Error(`Need ${check.shortfall} more credits`);\n * }\n *\n * // Reserve and execute\n * const reservation = await service.reserveCredits(userId, 10, 'story_generation');\n * try {\n * await doWork();\n * await service.commitCredits(userId, reservation.id);\n * } catch (error) {\n * await service.releaseCredits(userId, reservation.id);\n * throw error;\n * }\n * ```\n */\n\n// ==================== Core ====================\nexport * from \"./core/index.js\";\n\n// ==================== Config ====================\nexport {\n getConfig,\n initializeConfig,\n loadConfigFromEnv,\n getValidOperationTypes,\n isValidOperationType,\n getConfigOperationCost,\n getConfigTierConfig,\n getConfigMonthlyLimit,\n isFeatureEnabled,\n resetConfig,\n getOperationLabel,\n getOperationLabels,\n type CreditSystemConfig,\n} from \"./config/index.js\";\n\nexport {\n registerConfigProvider,\n getConfigProvider,\n clearConfigProvider,\n type ICreditConfigProvider,\n} from \"./config/provider.js\";\n\nexport {\n getOperationCosts,\n getOperationCost,\n getTierConfig,\n getMonthlyLimit,\n getDefaultFreeCredits,\n getReservationExpiryMs,\n getOperationCostsWithLabels,\n type OperationCostInfo,\n} from \"./config/costs.js\";\n\n// ==================== Repository ====================\n// Export types and classes (excluding toDate which is already exported from core)\nexport type {\n ICreditRepository,\n CreditRepositoryFactory,\n CreateReservationInput,\n CreateTransactionInput,\n CreateUsageLogInput,\n CreateJournalEntryInput,\n UsageLogQuery,\n JournalEntryQuery,\n CreditBalanceUpdate,\n TierUpdateInput,\n} from \"./repository/types.js\";\nexport { toClientUserCredits } from \"./repository/types.js\";\nexport { generateId, getNextMonthlyReset } from \"./repository/utils.js\";\nexport {\n InMemoryCreditRepository,\n createInMemoryCreditRepository,\n} from \"./repository/memory/index.js\";\n\n// ==================== Auth ====================\nexport * from \"./auth/index.js\";\n\n// ==================== Service ====================\nexport * from \"./service/index.js\";\n\n// ==================== Adapters ====================\nexport * from \"./adapters/index.js\";\n\n// ==================== Notifications ====================\nexport {\n registerNotificationHandler,\n clearNotificationHandlers,\n clearCooldownState,\n checkAndNotifyLowBalance,\n notifySubscriptionExpiring,\n notifySubscriptionExpired,\n ConsoleNotificationHandler,\n type CreditNotificationEvent,\n type ICreditNotificationHandler,\n} from \"./notifications/index.js\";\n\n// ==================== SDK ====================\n// Export SDK (excluding types that conflict with core)\nexport { CreditsClient } from \"./sdk/client.js\";\nexport { AdminCreditsClient } from \"./sdk/admin-client.js\";\nexport type {\n CreditsClientConfig,\n AdminCreditsClientConfig,\n UserCredits,\n CreditReservation,\n CreditCheckResult as SDKCreditCheckResult,\n ReservationResult,\n PaginationOptions,\n UsageHistoryEntry as SDKUsageHistoryEntry,\n UsageHistoryResponse as SDKUsageHistoryResponse,\n ApiResponse,\n SDKErrorCodeType,\n SubscriptionTier as SDKSubscriptionTier,\n} from \"./sdk/types.js\";\nexport type {\n CreditsConfig,\n ListUsersResponse,\n ListUsersOptions,\n} from \"./sdk/admin-client.js\";\nexport { SDKErrorCode } from \"./sdk/types.js\";\nexport {\n CreditsSDKError,\n NetworkError,\n AuthenticationError,\n AuthorizationError,\n InsufficientCreditsError as SDKInsufficientCreditsError,\n ReservationNotFoundError as SDKReservationNotFoundError,\n ReservationExpiredError as SDKReservationExpiredError,\n ValidationError,\n ServerError,\n parseApiError,\n} from \"./sdk/errors.js\";\n\n// ==================== Utils ====================\nexport {\n getTotalCredits,\n generateRequestId,\n parseCreditError,\n isCreditErrorMessage,\n isPreviewMode,\n createDummyReservation,\n type CreditErrorInfo,\n} from \"./utils/index.js\";\n","/**\n * Portable credits types - framework and database agnostic\n *\n * These types use standard JavaScript types (string, Date) instead of\n * Firebase-specific types (Timestamp). This makes the credits system\n * portable to other environments without Firebase dependencies.\n *\n * Note: Firestore Timestamp is handled by the credits-firestore adapter.\n */\n\n// ==================== Utility Functions ====================\n\n/**\n * Calculate available credits from balance, bonus, and reserved amounts.\n *\n * Available = balance + bonusCredits - reserved\n *\n * @param balance - Regular monthly credits\n * @param bonusCredits - Purchased/admin credits\n * @param reserved - Credits locked for in-flight operations\n * @returns Available credits for new operations\n */\nexport function calculateAvailableCredits(\n balance: number,\n bonusCredits: number,\n reserved: number\n): number {\n return balance + bonusCredits - reserved;\n}\n\n/**\n * Convert various timestamp formats to ISO string.\n *\n * Handles:\n * - Date objects\n * - ISO string (returned as-is)\n * - Timestamp-like objects with toDate() method (Firebase)\n * - undefined/null (returns current time)\n *\n * @param value - Value to convert\n * @returns ISO 8601 string\n */\nexport function toPortableTimestamp(value: unknown): string {\n if (!value) {\n return new Date().toISOString();\n }\n\n if (typeof value === \"string\") {\n return value;\n }\n\n if (value instanceof Date) {\n return value.toISOString();\n }\n\n if (\n typeof value === \"object\" &&\n value !== null &&\n \"toDate\" in value &&\n typeof (value as { toDate: () => Date }).toDate === \"function\"\n ) {\n return (value as { toDate: () => Date }).toDate().toISOString();\n }\n\n return new Date().toISOString();\n}\n\n/**\n * Convert various timestamp formats to Date object.\n *\n * Handles:\n * - Date objects (returned as-is)\n * - ISO string\n * - Timestamp-like objects with toDate() method (Firebase)\n * - Invalid values (returns current date)\n *\n * @param value - Value to convert\n * @returns Date object\n */\nexport function toDate(value: unknown): Date {\n if (value instanceof Date) {\n return value;\n }\n\n if (typeof value === \"string\") {\n return new Date(value);\n }\n\n if (\n typeof value === \"object\" &&\n value !== null &&\n \"toDate\" in value &&\n typeof (value as { toDate: () => Date }).toDate === \"function\"\n ) {\n return (value as { toDate: () => Date }).toDate();\n }\n\n return new Date();\n}\n\n// ==================== Subscription Types ====================\n\n/**\n * Subscription tiers for the credits system\n */\nexport type SubscriptionTier = \"free\" | \"basic\" | \"premium\" | \"unlimited\";\n\n// ==================== Operation Types ====================\n\n/**\n * Operation types for cost tracking (provider-agnostic)\n *\n * Dynamic string type - any operation configured in the system is valid.\n */\nexport type CreditOperationType = string;\n\n/**\n * AI providers\n */\nexport type AIProviderType = \"gemini\";\n\n/**\n * Resource type for usage logging\n */\nexport type ResourceType = string;\n\n// ==================== User Credits ====================\n\n/**\n * Portable user credits balance\n *\n * Uses ISO string timestamps instead of Firebase Timestamp.\n * This type is safe for JSON serialization and works across frameworks.\n */\nexport interface PortableUserCredits {\n userId: string;\n /** Current available balance (monthly credits, reset each month) */\n balance: number;\n /** Bonus credits from purchases/admin (never reset, persist until used) */\n bonusCredits: number;\n /** Credits currently reserved for in-flight operations */\n reserved: number;\n /** User's subscription tier */\n tier: SubscriptionTier;\n /** Monthly credit limit based on tier (0 = unlimited) */\n monthlyLimit: number;\n /** Credits used this month (resets monthly) */\n monthlyUsed: number;\n /** When monthly credits reset (start of next month) - ISO 8601 */\n monthlyResetAt: string;\n /** Subscription expiry (null/undefined for free tier) - ISO 8601 or null */\n subscriptionExpiresAt?: string | null;\n /** Creation timestamp - ISO 8601 */\n createdAt: string;\n /** Last update timestamp - ISO 8601 */\n updatedAt: string;\n}\n\n// ==================== Reservation Types ====================\n\n/**\n * Credit reservation status for two-phase commit\n */\nexport type ReservationStatus = \"reserved\" | \"committed\" | \"released\" | \"expired\";\n\n/**\n * Portable credit reservation\n *\n * Used for two-phase commit to prevent double-spending.\n */\nexport interface PortableReservation {\n id: string;\n userId: string;\n /** Amount of credits reserved */\n amount: number;\n /** Operation type for tracking */\n operationType: CreditOperationType;\n /** Current status of reservation */\n status: ReservationStatus;\n /** When reservation was created - ISO 8601 */\n createdAt: string;\n /** When reservation expires (for cleanup) - ISO 8601 */\n expiresAt: string;\n /** When reservation was committed/released - ISO 8601 or undefined */\n completedAt?: string;\n}\n\n// ==================== Result Types ====================\n\n/**\n * Result of a credit check operation\n */\nexport interface CreditCheckResult {\n /** Whether user has sufficient credits */\n hasCredits: boolean;\n /** Current balance (balance + bonusCredits) */\n balance: number;\n /** Required credits for operation */\n required: number;\n /** Shortfall if insufficient */\n shortfall: number;\n}\n\n/**\n * Result of monthly reset operation\n */\nexport interface MonthlyResetResult {\n /** Whether the reset was performed */\n wasReset: boolean;\n /** The user credits after the operation */\n credits: PortableUserCredits;\n}\n\n/**\n * Result of subscription expiry check\n */\nexport interface SubscriptionExpiryResult {\n /** Whether the subscription was downgraded */\n wasDowngraded: boolean;\n /** Whether user is in grace period */\n inGracePeriod: boolean;\n /** Days remaining in grace period (0 if not in grace) */\n graceDaysRemaining: number;\n /** The user credits after the operation */\n credits: PortableUserCredits;\n}\n\n// ==================== Transaction Types ====================\n\n/**\n * Credit transaction type\n */\nexport type TransactionType = \"purchase\" | \"subscription\" | \"bonus\" | \"refund\" | \"adjustment\";\n\n/**\n * Portable credit transaction\n */\nexport interface PortableTransaction {\n id: string;\n userId: string;\n type: TransactionType;\n /** Amount of credits (positive for additions, negative for deductions) */\n amount: number;\n description: string;\n /** Reference to payment provider */\n paymentRef?: string;\n previousBalance: number;\n newBalance: number;\n createdAt: string;\n}\n\n// ==================== Journal Types ====================\n\n/**\n * Source of a credit journal entry\n */\nexport type CreditSource =\n | \"operation_commit\"\n | \"operation_release\"\n | \"purchase\"\n | \"subscription_grant\"\n | \"subscription_upgrade\"\n | \"subscription_downgrade\"\n | \"monthly_reset\"\n | \"bonus\"\n | \"refund\"\n | \"admin_adjustment\"\n | \"expiry\"\n | \"reservation_expired\";\n\n/**\n * Type of reference for a journal entry\n */\nexport type JournalReferenceType =\n | \"reservation\"\n | \"transaction\"\n | \"adjustment\"\n | \"reset\"\n | \"subscription\";\n\n/**\n * Portable credit journal entry for audit trail\n */\nexport interface PortableJournalEntry {\n id: string;\n userId: string;\n /** Type of entry - debit decreases balance, credit increases balance */\n entryType: \"debit\" | \"credit\";\n /** Amount of credits involved */\n amount: number;\n /** Balance after this entry */\n balanceAfter: number;\n /** Source of the credit change */\n source: CreditSource;\n /** Reference ID (e.g., reservationId, transactionId) */\n referenceId: string;\n /** Type of reference */\n referenceType: JournalReferenceType;\n /** Human-readable description */\n description: string;\n /** Additional metadata */\n metadata?: Record<string, unknown>;\n /** When this entry was created - ISO 8601 */\n createdAt: string;\n}\n\n// ==================== Usage Types ====================\n\n/**\n * Portable usage log entry\n */\nexport interface PortableUsageLog {\n id: string;\n userId: string;\n operationType: CreditOperationType;\n provider: AIProviderType;\n creditsUsed: number;\n success: boolean;\n errorMessage?: string;\n resourceId?: string;\n resourceType?: ResourceType;\n requestId?: string;\n metadata?: Record<string, unknown>;\n createdAt: string;\n}\n\n/**\n * User-friendly usage history entry\n */\nexport interface UsageHistoryEntry {\n id: string;\n /** Type of entry for display */\n type: \"usage\" | \"purchase\" | \"bonus\" | \"reset\" | \"refund\" | \"adjustment\";\n /** Positive = earned, Negative = spent */\n creditsChange: number;\n /** Balance after this entry */\n balanceAfter: number;\n /** Human-readable description */\n description: string;\n /** When this occurred - ISO 8601 */\n createdAt: string;\n}\n\n/**\n * Paginated usage history response\n */\nexport interface UsageHistoryResponse {\n entries: UsageHistoryEntry[];\n pagination: {\n total: number;\n limit: number;\n offset: number;\n hasMore: boolean;\n };\n}\n\n// ==================== Tier Configuration ====================\n\n/**\n * Tier configuration for credit limits and pricing\n */\nexport interface TierConfig {\n tier: SubscriptionTier;\n monthlyCredits: number;\n priceUsd: number;\n features: string[];\n}\n\n// ==================== Options Types ====================\n\n/**\n * Options for withCredits HOF\n */\nexport interface WithCreditsOptions {\n /** Operation type for cost lookup */\n operationType: CreditOperationType;\n /** AI provider (defaults to \"gemini\") */\n provider?: AIProviderType;\n /** Custom cost override (use with caution) */\n customCost?: number;\n /** Resource ID for usage logging */\n resourceId?: string;\n /** Resource type for usage logging */\n resourceType?: ResourceType;\n}\n\n// ==================== Constants ====================\n","/**\n * Credit system error classes - framework agnostic\n *\n * Provides typed error handling for credit operations.\n * These errors can be used across different environments.\n */\n\n/**\n * Error codes for credit operations\n */\nexport const CreditErrorCode = {\n INSUFFICIENT_CREDITS: \"INSUFFICIENT_CREDITS\",\n RESERVATION_NOT_FOUND: \"RESERVATION_NOT_FOUND\",\n RESERVATION_EXPIRED: \"RESERVATION_EXPIRED\",\n RESERVATION_ALREADY_PROCESSED: \"RESERVATION_ALREADY_PROCESSED\",\n USER_NOT_FOUND: \"USER_NOT_FOUND\",\n INVALID_OPERATION_TYPE: \"INVALID_OPERATION_TYPE\",\n CONFIGURATION_ERROR: \"CONFIGURATION_ERROR\",\n DATABASE_ERROR: \"DATABASE_ERROR\",\n} as const;\n\nexport type CreditErrorCodeType = (typeof CreditErrorCode)[keyof typeof CreditErrorCode];\n\n/**\n * Custom error class for credit operations\n *\n * Provides structured error information including:\n * - Error code for programmatic handling\n * - Human-readable message\n * - Optional details for debugging\n */\nexport class CreditError extends Error {\n public readonly code: CreditErrorCodeType;\n public readonly details?: Record<string, unknown>;\n\n constructor(\n message: string,\n code: CreditErrorCodeType,\n details?: Record<string, unknown>\n ) {\n super(message);\n this.name = \"CreditError\";\n this.code = code;\n this.details = details;\n\n // Maintains proper stack trace for where error was thrown\n if (Error.captureStackTrace) {\n Error.captureStackTrace(this, CreditError);\n }\n }\n\n /**\n * Convert to JSON-serializable object\n */\n toJSON(): { name: string; code: string; message: string; details?: Record<string, unknown> } {\n return {\n name: this.name,\n code: this.code,\n message: this.message,\n ...(this.details && { details: this.details }),\n };\n }\n}\n\n/**\n * Type guard to check if an error is a CreditError\n */\nexport function isCreditError(error: unknown): error is CreditError {\n return error instanceof CreditError;\n}\n\n/**\n * Check if an error represents insufficient credits\n *\n * Works with both CreditError and regular Error instances.\n * Useful for handling errors from different sources.\n */\nexport function isInsufficientCreditsError(error: unknown): boolean {\n if (isCreditError(error)) {\n return error.code === CreditErrorCode.INSUFFICIENT_CREDITS;\n }\n\n if (error instanceof Error) {\n return error.message.toLowerCase().includes(\"insufficient credits\");\n }\n\n return false;\n}\n\n/**\n * Create an insufficient credits error\n */\nexport function createInsufficientCreditsError(\n required: number,\n available: number\n): CreditError {\n return new CreditError(\n `Insufficient credits: available ${available}, required ${required}`,\n CreditErrorCode.INSUFFICIENT_CREDITS,\n { required, available, shortfall: required - available }\n );\n}\n\n/**\n * Create a reservation not found error\n */\nexport function createReservationNotFoundError(reservationId: string): CreditError {\n return new CreditError(\n `Reservation ${reservationId} not found`,\n CreditErrorCode.RESERVATION_NOT_FOUND,\n { reservationId }\n );\n}\n\n/**\n * Create a reservation expired error\n */\nexport function createReservationExpiredError(reservationId: string): CreditError {\n return new CreditError(\n `Reservation ${reservationId} has expired`,\n CreditErrorCode.RESERVATION_EXPIRED,\n { reservationId }\n );\n}\n\n/**\n * Create a reservation already processed error\n */\nexport function createReservationAlreadyProcessedError(\n reservationId: string,\n status: string\n): CreditError {\n return new CreditError(\n `Reservation ${reservationId} has already been ${status}`,\n CreditErrorCode.RESERVATION_ALREADY_PROCESSED,\n { reservationId, status }\n );\n}\n\n/**\n * Create a user not found error\n */\nexport function createUserNotFoundError(userId: string): CreditError {\n return new CreditError(\n `User ${userId} not found`,\n CreditErrorCode.USER_NOT_FOUND,\n { userId }\n );\n}\n\n/**\n * Create an invalid operation type error\n */\nexport function createInvalidOperationTypeError(\n operationType: string,\n validTypes: string[]\n): CreditError {\n return new CreditError(\n `Invalid operation type: ${operationType}. Valid types: ${validTypes.join(\", \")}`,\n CreditErrorCode.INVALID_OPERATION_TYPE,\n { operationType, validTypes }\n );\n}\n","/**\n * Deferred execution abstraction - framework agnostic\n *\n * Provides a way to schedule work to run after the current operation completes.\n * Different frameworks can provide their own implementations:\n * - Next.js: uses `after()` from `next/server`\n * - Generic: uses `setImmediate()` or `setTimeout()`\n * - Testing: can use synchronous execution\n */\n\n/**\n * Interface for deferred execution\n *\n * Implementations should schedule the function to run asynchronously\n * without blocking the current operation.\n */\nexport interface DeferredExecutor {\n /**\n * Schedule a function to run asynchronously\n *\n * The function should be executed after the current operation completes.\n * Errors should be caught and logged, not propagated.\n *\n * @param fn - Async function to execute\n */\n defer(fn: () => Promise<void>): void;\n}\n\n/**\n * Generic deferred executor using setImmediate/setTimeout\n *\n * Works in Node.js and browser environments.\n * Suitable for non-framework environments or testing.\n */\nexport const genericDeferred: DeferredExecutor = {\n defer(fn: () => Promise<void>): void {\n // Use setImmediate in Node.js, setTimeout as fallback\n const scheduler = typeof setImmediate !== \"undefined\" ? setImmediate : setTimeout;\n scheduler(() => {\n fn().catch((error) => {\n console.error(\"[Credits] Deferred task error:\", error);\n });\n });\n },\n};\n\n/**\n * Synchronous deferred executor\n *\n * Executes the function immediately (fire-and-forget).\n * Useful for testing or when deferred execution isn't needed.\n */\nexport const synchronousDeferred: DeferredExecutor = {\n defer(fn: () => Promise<void>): void {\n fn().catch((error) => {\n console.error(\"[Credits] Deferred task error:\", error);\n });\n },\n};\n\n/**\n * Create a custom deferred executor\n *\n * Allows frameworks to provide their own scheduling mechanism.\n *\n * @example\n * ```typescript\n * // Next.js adapter\n * import { after } from 'next/server';\n *\n * const nextJsDeferred = createDeferredExecutor((fn) => after(fn));\n * ```\n *\n * @param schedule - Function that schedules async work\n * @returns DeferredExecutor instance\n */\nexport function createDeferredExecutor(\n schedule: (fn: () => Promise<void>) => void\n): DeferredExecutor {\n return {\n defer: schedule,\n };\n}\n\n/**\n * No-op deferred executor\n *\n * Discards deferred work. Useful when you want to disable\n * background tasks (e.g., in certain test scenarios).\n */\nexport const noopDeferred: DeferredExecutor = {\n defer(_fn: () => Promise<void>): void {\n // Intentionally do nothing\n },\n};\n","import { z } from \"zod\";\nimport type { SubscriptionTier, TierConfig } from \"../core/types.js\";\nimport { getConfigProvider } from \"./provider.js\";\n\n/**\n * Schema for tier configuration\n */\nconst tierConfigSchema = z.object({\n tier: z.enum([\"free\", \"basic\", \"premium\", \"unlimited\"]),\n monthlyCredits: z.number().min(0),\n priceUsd: z.number().min(0),\n features: z.array(z.string()),\n});\n\n/**\n * Schema for credit system configuration\n */\nconst creditSystemConfigSchema = z.object({\n /**\n * Operation costs in credits\n * Keys are operation type names (any non-empty string)\n * Values are positive numbers representing credit cost\n */\n operationCosts: z.record(\n z.string().min(1),\n z.number().positive()\n ),\n\n /**\n * Display labels for operation types (English canonical labels).\n * Falls back to auto-generating from snake_case keys if not provided.\n */\n operationLabels: z.record(\n z.string().min(1),\n z.string().min(1)\n ).optional().default({}),\n\n /**\n * Tier configurations\n */\n tierConfigs: z.record(\n z.enum([\"free\", \"basic\", \"premium\", \"unlimited\"]),\n tierConfigSchema\n ),\n\n /**\n * Reservation expiry time in milliseconds\n */\n reservationExpiryMs: z.number().positive().default(5 * 60 * 1000),\n\n /**\n * Default credits for new users (free tier)\n */\n defaultFreeCredits: z.number().positive().default(25),\n\n /**\n * Grace period in days after subscription expires before downgrade\n */\n subscriptionGracePeriodDays: z.number().min(0).default(3),\n\n /**\n * Low balance notification threshold\n */\n lowBalanceThreshold: z.number().min(0).default(10),\n\n /**\n * Cooldown in hours between low balance notifications\n */\n lowBalanceNotificationCooldownHours: z.number().positive().default(24),\n\n /**\n * Feature flags for optional features\n */\n features: z.object({\n journalEntries: z.boolean().default(true),\n notifications: z.boolean().default(true),\n subscriptionExpiry: z.boolean().default(true),\n usageHistory: z.boolean().default(true),\n }).default({\n journalEntries: true,\n notifications: true,\n subscriptionExpiry: true,\n usageHistory: true,\n }),\n});\n\n/**\n * Credit system configuration type\n */\nexport type CreditSystemConfig = z.infer<typeof creditSystemConfigSchema>;\n\n/**\n * Default configuration\n */\nconst DEFAULT_CONFIG: CreditSystemConfig = {\n operationCosts: {\n story_generation: 5,\n conversation: 2,\n image_generation: 10,\n template_generation: 10,\n },\n operationLabels: {\n story_generation: \"Story generation\",\n conversation: \"Conversation\",\n image_generation: \"Image generation\",\n template_generation: \"Template generation\",\n },\n tierConfigs: {\n free: {\n tier: \"free\",\n monthlyCredits: 25,\n priceUsd: 0,\n features: [\n \"25 credits per month\",\n \"Basic story generation\",\n \"Standard image quality\",\n ],\n },\n basic: {\n tier: \"basic\",\n monthlyCredits: 200,\n priceUsd: 9.99,\n features: [\n \"200 credits per month\",\n \"All story features\",\n \"High quality images\",\n \"Priority support\",\n ],\n },\n premium: {\n tier: \"premium\",\n monthlyCredits: 500,\n priceUsd: 19.99,\n features: [\n \"500 credits per month\",\n \"All features\",\n \"Highest quality images\",\n \"Early access to new features\",\n \"Priority support\",\n ],\n },\n unlimited: {\n tier: \"unlimited\",\n monthlyCredits: 0, // 0 = unlimited\n priceUsd: 49.99,\n features: [\n \"Unlimited credits\",\n \"All features\",\n \"Highest quality images\",\n \"Early access to new features\",\n \"Dedicated support\",\n ],\n },\n },\n reservationExpiryMs: 5 * 60 * 1000, // 5 minutes\n defaultFreeCredits: 25,\n subscriptionGracePeriodDays: 3,\n lowBalanceThreshold: 10,\n lowBalanceNotificationCooldownHours: 24,\n features: {\n journalEntries: true,\n notifications: true,\n subscriptionExpiry: true,\n usageHistory: true,\n },\n};\n\n/**\n * Current configuration (can be overridden)\n */\nlet currentConfig: CreditSystemConfig = DEFAULT_CONFIG;\n\n/**\n * Load configuration from environment variables\n * Can be extended to load from other sources (file, remote config, etc.)\n *\n * Operation costs are loaded from CREDITS_OPERATION_COSTS as JSON:\n * CREDITS_OPERATION_COSTS={\"story_generation\":5,\"image_generation\":10}\n */\nexport function loadConfigFromEnv(): Partial<CreditSystemConfig> {\n const envConfig: Partial<CreditSystemConfig> = {};\n\n // Load operation costs from env as JSON\n const operationCostsEnv = process.env.CREDITS_OPERATION_COSTS;\n if (operationCostsEnv) {\n try {\n const parsed = JSON.parse(operationCostsEnv);\n if (typeof parsed === \"object\" && parsed !== null) {\n envConfig.operationCosts = {\n ...DEFAULT_CONFIG.operationCosts,\n ...parsed,\n };\n }\n } catch {\n console.warn(\"[Credits Config] Failed to parse CREDITS_OPERATION_COSTS env var as JSON\");\n }\n }\n\n // Load other settings from env\n const reservationExpiry = process.env.CREDITS_RESERVATION_EXPIRY_MS;\n if (reservationExpiry) {\n envConfig.reservationExpiryMs = parseInt(reservationExpiry, 10);\n }\n\n const defaultCredits = process.env.CREDITS_DEFAULT_FREE;\n if (defaultCredits) {\n envConfig.defaultFreeCredits = parseInt(defaultCredits, 10);\n }\n\n const gracePeriod = process.env.CREDITS_GRACE_PERIOD_DAYS;\n if (gracePeriod) {\n envConfig.subscriptionGracePeriodDays = parseInt(gracePeriod, 10);\n }\n\n const lowBalanceThreshold = process.env.CREDITS_LOW_BALANCE_THRESHOLD;\n if (lowBalanceThreshold) {\n envConfig.lowBalanceThreshold = parseInt(lowBalanceThreshold, 10);\n }\n\n // Load feature flags\n const featuresEnv = process.env.CREDITS_FEATURES;\n if (featuresEnv) {\n try {\n const features = JSON.parse(featuresEnv);\n envConfig.features = {\n ...DEFAULT_CONFIG.features,\n ...features,\n };\n } catch {\n console.warn(\"[Credits Config] Failed to parse CREDITS_FEATURES env var\");\n }\n }\n\n return envConfig;\n}\n\n/**\n * Initialize configuration\n * Merges default config with environment overrides\n */\nexport function initializeConfig(overrides?: Partial<CreditSystemConfig>): CreditSystemConfig {\n const envConfig = loadConfigFromEnv();\n\n const mergedConfig = {\n ...DEFAULT_CONFIG,\n ...envConfig,\n ...overrides,\n operationCosts: {\n ...DEFAULT_CONFIG.operationCosts,\n ...envConfig.operationCosts,\n ...overrides?.operationCosts,\n },\n operationLabels: {\n ...DEFAULT_CONFIG.operationLabels,\n ...envConfig.operationLabels,\n ...overrides?.operationLabels,\n },\n tierConfigs: {\n ...DEFAULT_CONFIG.tierConfigs,\n ...envConfig.tierConfigs,\n ...overrides?.tierConfigs,\n },\n features: {\n ...DEFAULT_CONFIG.features,\n ...envConfig.features,\n ...overrides?.features,\n },\n };\n\n // Validate configuration\n const result = creditSystemConfigSchema.safeParse(mergedConfig);\n if (!result.success) {\n console.error(\"[Credits Config] Invalid configuration:\", result.error.issues);\n throw new Error(\"Invalid credit system configuration\");\n }\n\n currentConfig = result.data;\n return currentConfig;\n}\n\n/**\n * Get the current configuration.\n * If an external provider is registered, delegates to it.\n * Otherwise falls back to the local currentConfig.\n */\nexport function getConfig(): CreditSystemConfig {\n const provider = getConfigProvider();\n if (provider) {\n return provider.getConfig();\n }\n return currentConfig;\n}\n\n/**\n * Get all valid operation types from configuration\n * Returns the keys of the operationCosts object\n */\nexport function getValidOperationTypes(): string[] {\n return Object.keys(getConfig().operationCosts);\n}\n\n/**\n * Check if an operation type is valid (configured in the system)\n * @param type - The operation type to check\n * @returns true if the operation type is configured\n */\nexport function isValidOperationType(type: string): boolean {\n return type in getConfig().operationCosts;\n}\n\n/**\n * Get operation cost from configuration\n * @throws Error if the operation type is not configured\n */\nexport function getConfigOperationCost(operationType: string): number {\n const cost = getConfig().operationCosts[operationType];\n if (cost === undefined) {\n throw new Error(\n `Unknown operation type: ${operationType}. Valid types: ${getValidOperationTypes().join(\", \")}`\n );\n }\n return cost;\n}\n\n/**\n * Get tier configuration from configuration\n */\nexport function getConfigTierConfig(tier: SubscriptionTier): TierConfig {\n return getConfig().tierConfigs[tier]!;\n}\n\n/**\n * Get monthly limit for a tier from configuration\n * Returns Infinity for unlimited tier\n */\nexport function getConfigMonthlyLimit(tier: SubscriptionTier): number {\n const config = getConfig().tierConfigs[tier]!;\n return config.monthlyCredits === 0 ? Infinity : config.monthlyCredits;\n}\n\n/**\n * Check if a feature is enabled\n */\nexport function isFeatureEnabled(feature: keyof CreditSystemConfig[\"features\"]): boolean {\n return getConfig().features[feature];\n}\n\n/**\n * Convert a snake_case string to Title Case.\n * E.g., \"story_generation\" -> \"Story Generation\"\n */\nfunction snakeCaseToTitleCase(str: string): string {\n return str\n .replace(/_/g, \" \")\n .replace(/\\b\\w/g, (c) => c.toUpperCase());\n}\n\n/**\n * Get the display label for an operation type.\n * Falls back to converting snake_case to Title Case if no label is configured.\n */\nexport function getOperationLabel(operationType: string): string {\n const labels = getConfig().operationLabels;\n if (labels && operationType in labels) {\n return labels[operationType];\n }\n return snakeCaseToTitleCase(operationType);\n}\n\n/**\n * Get all operation labels as a record.\n * For any operation in operationCosts that lacks a label, generates one from the key.\n */\nexport function getOperationLabels(): Record<string, string> {\n const config = getConfig();\n const result: Record<string, string> = {};\n for (const key of Object.keys(config.operationCosts)) {\n result[key] = config.operationLabels?.[key]\n ?? snakeCaseToTitleCase(key);\n }\n return result;\n}\n\n/**\n * Reset configuration to defaults (for testing)\n */\nexport function resetConfig(): void {\n currentConfig = DEFAULT_CONFIG;\n}\n\n// Initialize configuration on module load\ninitializeConfig();\n","import type { CreditSystemConfig } from \"./index.js\";\n\n/** Interface for external config providers (e.g. Firestore-backed) */\nexport interface ICreditConfigProvider {\n getConfig(): CreditSystemConfig;\n}\n\nlet configProvider: ICreditConfigProvider | null = null;\n\n/** Register an external config provider. Called once at app startup. */\nexport function registerConfigProvider(provider: ICreditConfigProvider): void {\n configProvider = provider;\n}\n\n/** Get the registered provider, or null if none registered. */\nexport function getConfigProvider(): ICreditConfigProvider | null {\n return configProvider;\n}\n\n/** Clear registered provider (for testing). */\nexport function clearConfigProvider(): void {\n configProvider = null;\n}\n","/**\n * Core credit operations - framework agnostic\n *\n * Contains the business logic for credit operations that can be\n * used by any adapter or service implementation.\n */\n\nimport type { ICreditRepository } from \"../repository/types.js\";\nimport type { PortableReservation } from \"./types.js\";\nimport { getOperationLabel } from \"../config/index.js\";\n\n/**\n * Commit a reservation with journal entry\n *\n * @param repository - The credit repository\n * @param userId - User ID\n * @param reservationId - Reservation to commit\n */\nexport async function commitReservationWithJournal(\n repository: ICreditRepository,\n userId: string,\n reservationId: string\n): Promise<void> {\n // Get the reservation to know the amount\n const reservation = await repository.getReservation(userId, reservationId);\n if (!reservation) {\n throw new Error(`Reservation ${reservationId} not found`);\n }\n\n // Commit the reservation atomically\n await repository.commitReservationAtomic(userId, reservationId);\n\n // Create journal entry\n const credits = await repository.getUserCredits(userId);\n if (credits) {\n await repository.createJournalEntry({\n userId,\n entryType: \"debit\",\n amount: reservation.amount,\n balanceAfter: credits.balance,\n source: \"operation_commit\",\n referenceId: reservationId,\n referenceType: \"reservation\",\n description: `Committed ${reservation.amount} credits for ${getOperationLabel(reservation.operationType)}`,\n metadata: {\n operationType: reservation.operationType,\n },\n });\n }\n}\n\n/**\n * Release a reservation with journal entry\n *\n * @param repository - The credit repository\n * @param userId - User ID\n * @param reservationId - Reservation to release\n */\nexport async function releaseReservationWithJournal(\n repository: ICreditRepository,\n userId: string,\n reservationId: string\n): Promise<void> {\n // Get the reservation to check its state\n const reservation = await repository.getReservation(userId, reservationId);\n\n // Release the reservation atomically\n await repository.releaseReservationAtomic(userId, reservationId);\n\n // Create journal entry only if reservation was in reserved state\n if (reservation?.status === \"reserved\") {\n const credits = await repository.getUserCredits(userId);\n if (credits) {\n await repository.createJournalEntry({\n userId,\n entryType: \"credit\",\n amount: 0, // No actual credits returned (they were reserved, not spent)\n balanceAfter: credits.balance,\n source: \"operation_release\",\n referenceId: reservationId,\n referenceType: \"reservation\",\n description: `Released ${reservation.amount} reserved credits for ${getOperationLabel(reservation.operationType)}`,\n metadata: {\n operationType: reservation.operationType,\n amount: reservation.amount,\n },\n });\n }\n }\n}\n\n/**\n * Reserve credits for an operation\n *\n * @param repository - The credit repository\n * @param userId - User ID\n * @param amount - Credits to reserve\n * @param operationType - Type of operation\n * @param expiryMs - Reservation expiry time in milliseconds\n * @returns The reservation\n */\nexport async function reserveCreditsForOperation(\n repository: ICreditRepository,\n userId: string,\n amount: number,\n operationType: string,\n expiryMs: number = 5 * 60 * 1000\n): Promise<PortableReservation> {\n const expiresAt = new Date(Date.now() + expiryMs);\n return repository.reserveCreditsAtomic(userId, amount, operationType, expiresAt);\n}\n","import type { SubscriptionTier, TierConfig } from \"../core/types.js\";\nimport {\n getConfig,\n getConfigOperationCost,\n getConfigTierConfig,\n getConfigMonthlyLimit,\n getValidOperationTypes,\n isValidOperationType,\n getOperationLabel,\n} from \"./index.js\";\n\n/**\n * Get operation costs as a record\n * Returns all configured operation costs from the current config\n */\nexport function getOperationCosts(): Record<string, number> {\n return { ...getConfig().operationCosts };\n}\n\n/**\n * Get the credit cost for an operation type\n * Uses the configuration system for dynamic costs\n *\n * @param type - Operation type (must be configured in the system)\n * @returns Credit cost for the operation\n * @throws Error if the operation type is not configured\n */\nexport function getOperationCost(type: string): number {\n return getConfigOperationCost(type);\n}\n\n// Re-export validation helpers for convenience\nexport { getValidOperationTypes, isValidOperationType };\n\n/**\n * Get tier configuration\n * Uses the configuration system for dynamic configs\n *\n * @param tier - Subscription tier\n * @returns Tier configuration\n */\nexport function getTierConfig(tier: SubscriptionTier): TierConfig {\n return getConfigTierConfig(tier);\n}\n\n/**\n * Get monthly credit limit for a tier\n * Returns Infinity for unlimited tier\n * Uses the configuration system for dynamic limits\n *\n * @param tier - Subscription tier\n * @returns Monthly credit limit\n */\nexport function getMonthlyLimit(tier: SubscriptionTier): number {\n return getConfigMonthlyLimit(tier);\n}\n\n/**\n * Get default free credits from configuration\n */\nexport function getDefaultFreeCredits(): number {\n return getConfig().defaultFreeCredits;\n}\n\n/**\n * Get reservation expiry time from configuration\n */\nexport function getReservationExpiryMs(): number {\n return getConfig().reservationExpiryMs;\n}\n\n/**\n * Information about an operation's cost and display label\n */\nexport interface OperationCostInfo {\n key: string;\n cost: number;\n label: string;\n}\n\n/**\n * Get all operation costs with their display labels.\n * Returns a record keyed by operation type with cost + label.\n */\nexport function getOperationCostsWithLabels(): Record<string, OperationCostInfo> {\n const config = getConfig();\n const result: Record<string, OperationCostInfo> = {};\n for (const [key, cost] of Object.entries(config.operationCosts)) {\n result[key] = {\n key,\n cost,\n label: getOperationLabel(key),\n };\n }\n return result;\n}\n","import type {\n PortableUserCredits,\n PortableReservation,\n PortableTransaction,\n PortableUsageLog,\n PortableJournalEntry,\n CreditOperationType,\n SubscriptionTier,\n ReservationStatus,\n AIProviderType,\n MonthlyResetResult,\n SubscriptionExpiryResult,\n CreditSource,\n JournalReferenceType,\n} from \"../core/types.js\";\n\n/**\n * Input for creating a credit reservation\n */\nexport interface CreateReservationInput {\n userId: string;\n amount: number;\n operationType: CreditOperationType;\n expiresAt: Date;\n}\n\n/**\n * Input for creating a credit transaction\n */\nexport interface CreateTransactionInput {\n userId: string;\n type: PortableTransaction[\"type\"];\n amount: number;\n description: string;\n paymentRef?: string;\n previousBalance: number;\n newBalance: number;\n}\n\n/**\n * Input for logging usage\n */\nexport interface CreateUsageLogInput {\n userId: string;\n operationType: CreditOperationType;\n provider: AIProviderType;\n creditsUsed: number;\n success: boolean;\n errorMessage?: string;\n resourceId?: string;\n resourceType?: string;\n requestId?: string;\n metadata?: Record<string, unknown>;\n}\n\n/**\n * Query options for usage logs\n */\nexport interface UsageLogQuery {\n userId?: string;\n operationType?: CreditOperationType;\n success?: boolean;\n limit?: number;\n offset?: number;\n startDate?: Date;\n endDate?: Date;\n}\n\n/**\n * Input for creating a journal entry\n */\nexport interface CreateJournalEntryInput {\n userId: string;\n entryType: \"debit\" | \"credit\";\n amount: number;\n balanceAfter: number;\n source: CreditSource;\n referenceId: string;\n referenceType: JournalReferenceType;\n description: string;\n metadata?: Record<string, unknown>;\n}\n\n/**\n * Query options for journal entries\n */\nexport interface JournalEntryQuery {\n userId: string;\n source?: CreditSource;\n referenceType?: JournalReferenceType;\n limit?: number;\n offset?: number;\n startDate?: Date;\n endDate?: Date;\n}\n\n/**\n * Partial update for user credits balance\n */\nexport interface CreditBalanceUpdate {\n balance?: number;\n bonusCredits?: number;\n reserved?: number;\n tier?: SubscriptionTier;\n monthlyLimit?: number;\n monthlyUsed?: number;\n monthlyResetAt?: Date | string;\n subscriptionExpiresAt?: Date | string | null;\n /** Increment balance by this amount (alternative to absolute value) */\n balanceIncrement?: number;\n /** Increment bonusCredits by this amount (alternative to absolute value) */\n bonusCreditsIncrement?: number;\n /** Increment reserved by this amount (alternative to absolute value) */\n reservedIncrement?: number;\n /** Increment monthlyUsed by this amount */\n monthlyUsedIncrement?: number;\n}\n\n/**\n * Input for tier update\n */\nexport interface TierUpdateInput {\n tier: SubscriptionTier;\n monthlyLimit: number;\n balance?: number;\n monthlyUsed?: number;\n subscriptionExpiresAt?: Date | string | null;\n}\n\n/**\n * Repository interface for credits database operations\n *\n * Implementations can use any database (Firestore, PostgreSQL, etc.)\n * All methods should handle their own error handling and transactions\n */\nexport interface ICreditRepository {\n // ==================== User Credits ====================\n\n /**\n * Get user credits balance\n * @param userId - User ID\n * @returns User credits or null if not found\n */\n getUserCredits(userId: string): Promise<PortableUserCredits | null>;\n\n /**\n * Initialize credits for a new user\n * @param userId - User ID\n * @param tier - Initial subscription tier\n * @param initialBalance - Initial credit balance\n * @returns Initialized user credits\n */\n initializeUserCredits(\n userId: string,\n tier: SubscriptionTier,\n initialBalance: number\n ): Promise<PortableUserCredits>;\n\n /**\n * Update user credits balance\n * @param userId - User ID\n * @param updates - Partial updates to apply\n */\n updateUserCredits(userId: string, updates: CreditBalanceUpdate): Promise<void>;\n\n /**\n * Update user subscription tier\n * @param userId - User ID\n * @param input - Tier update data\n */\n updateUserTier(userId: string, input: TierUpdateInput): Promise<void>;\n\n // ==================== Reservations ====================\n\n /**\n * Create a credit reservation (phase 1 of two-phase commit)\n * @param input - Reservation data\n * @returns Created reservation\n */\n createReservation(input: CreateReservationInput): Promise<PortableReservation>;\n\n /**\n * Get a reservation by ID\n * @param userId - User ID\n * @param reservationId - Reservation ID\n * @returns Reservation or null\n */\n getReservation(userId: string, reservationId: string): Promise<PortableReservation | null>;\n\n /**\n * Update reservation status\n * @param userId - User ID\n * @param reservationId - Reservation ID\n * @param status - New status\n * @param completedAt - Completion timestamp\n */\n updateReservationStatus(\n userId: string,\n reservationId: string,\n status: ReservationStatus,\n completedAt?: Date\n ): Promise<void>;\n\n // ==================== Atomic Operations ====================\n\n /**\n * Reserve credits atomically (creates reservation + updates balance in transaction)\n * @param userId - User ID\n * @param amount - Credits to reserve\n * @param operationType - Operation type for tracking\n * @param expiresAt - Reservation expiry time\n * @returns Created reservation\n * @throws Error if insufficient credits\n */\n reserveCreditsAtomic(\n userId: string,\n amount: number,\n operationType: CreditOperationType,\n expiresAt: Date\n ): Promise<PortableReservation>;\n\n /**\n * Commit a reservation atomically (deducts credits + marks reservation committed)\n * @param userId - User ID\n * @param reservationId - Reservation ID\n * @throws Error if reservation not found or not in reserved state\n */\n commitReservationAtomic(userId: string, reservationId: string): Promise<void>;\n\n /**\n * Release a reservation atomically (releases reserved credits + marks reservation released)\n * @param userId - User ID\n * @param reservationId - Reservation ID\n */\n releaseReservationAtomic(userId: string, reservationId: string): Promise<void>;\n\n /**\n * Add credits atomically (creates transaction + updates balance)\n * @param userId - User ID\n * @param amount - Credits to add\n * @param description - Transaction description\n * @param paymentRef - Optional payment reference\n */\n addCreditsAtomic(\n userId: string,\n amount: number,\n description: string,\n paymentRef?: string\n ): Promise<void>;\n\n /**\n * Deduct credits from a user's balance atomically.\n *\n * Splits the deduction across `balance` and `bonusCredits`, draining\n * `balance` (monthly, resets each cycle) first so persistent bonus\n * credits survive longer. Runs in a single atomic transaction so\n * concurrent deducts cannot drive either field negative.\n *\n * Callers are responsible for writing any audit record (journal /\n * transaction) — this method only moves credits.\n *\n * @param userId - User ID\n * @param amount - Credits to deduct (positive)\n * @returns Combined totals (balance + bonusCredits) before and after.\n * @throws If user has no credits doc or insufficient credits\n */\n deductCreditsAtomic(\n userId: string,\n amount: number\n ): Promise<{ previousBalance: number; newBalance: number }>;\n\n // ==================== Transactions ====================\n\n /**\n * Create a credit transaction record\n * @param input - Transaction data\n * @returns Created transaction\n */\n createTransaction(input: CreateTransactionInput): Promise<PortableTransaction>;\n\n /**\n * Get user's transaction history\n * @param userId - User ID\n * @param limit - Max results\n * @param offset - Skip results\n * @returns List of transactions\n */\n getTransactions(\n userId: string,\n limit?: number,\n offset?: number\n ): Promise<PortableTransaction[]>;\n\n // ==================== Usage Logs ====================\n\n /**\n * Log a usage event\n * @param input - Usage log data\n * @returns Created usage log\n */\n logUsage(input: CreateUsageLogInput): Promise<PortableUsageLog>;\n\n /**\n * Query usage logs\n * @param query - Query parameters\n * @returns List of usage logs\n */\n getUsageLogs(query: UsageLogQuery): Promise<PortableUsageLog[]>;\n\n /**\n * Get usage log count (for pagination)\n * @param query - Query parameters (without limit/offset)\n * @returns Count of matching logs\n */\n getUsageLogsCount(query: Omit<UsageLogQuery, \"limit\" | \"offset\">): Promise<number>;\n\n // ==================== Cleanup Operations ====================\n\n /**\n * Find and expire reservations past their expiration time\n * Used by cron job to clean up stale reservations\n * @param batchSize - Maximum number of reservations to process per batch (default: 100)\n * @param maxIterations - Maximum number of pagination iterations to prevent infinite loops (default: 100)\n * @returns Cleanup results with counts and errors\n */\n findAndExpireReservations(batchSize?: number, maxIterations?: number): Promise<{\n expiredCount: number;\n creditsReleased: number;\n errors: string[];\n }>;\n\n // ==================== Atomic Monthly Reset ====================\n\n /**\n * Atomically perform monthly reset if needed\n * Uses optimistic locking to prevent race conditions\n *\n * @param userId - User ID\n * @param tier - User's current subscription tier (for determining new balance)\n * @param expectedResetAt - The expected monthlyResetAt value (for optimistic locking)\n * @returns Result indicating whether reset was performed and updated credits\n */\n atomicMonthlyReset(\n userId: string,\n tier: SubscriptionTier,\n expectedResetAt: Date | string\n ): Promise<MonthlyResetResult>;\n\n // ==================== Subscription Expiry ====================\n\n /**\n * Check and handle subscription expiry with grace period\n * Auto-downgrades expired subscriptions after grace period\n *\n * @param userId - User ID\n * @param gracePeriodDays - Days to allow after expiry before downgrade (default: 3)\n * @returns Result indicating whether downgrade occurred\n */\n checkAndHandleSubscriptionExpiry(\n userId: string,\n gracePeriodDays?: number\n ): Promise<SubscriptionExpiryResult>;\n\n // ==================== Journal Entries ====================\n\n /**\n * Create a journal entry for audit trail\n * @param input - Journal entry data\n * @returns Created journal entry\n */\n createJournalEntry(input: CreateJournalEntryInput): Promise<PortableJournalEntry>;\n\n /**\n * Get journal entries for a user\n * @param query - Query parameters\n * @returns List of journal entries\n */\n getJournalEntries(query: JournalEntryQuery): Promise<PortableJournalEntry[]>;\n\n /**\n * Get journal entry count for pagination\n * @param query - Query parameters (without limit/offset)\n * @returns Count of matching entries\n */\n getJournalEntriesCount(query: Omit<JournalEntryQuery, \"limit\" | \"offset\">): Promise<number>;\n}\n\n/**\n * Factory type for creating repository instances\n */\nexport type CreditRepositoryFactory = () => ICreditRepository;\n\n/**\n * Convert PortableUserCredits to client-safe format\n * Utility function that implementations can use\n */\nexport function toClientUserCredits(credits: PortableUserCredits): PortableUserCredits {\n // Already in portable format, just ensure all timestamps are ISO strings\n return {\n userId: credits.userId,\n balance: credits.balance,\n bonusCredits: credits.bonusCredits ?? 0,\n reserved: credits.reserved,\n tier: credits.tier,\n monthlyLimit: credits.monthlyLimit,\n monthlyUsed: credits.monthlyUsed,\n monthlyResetAt: toISOString(credits.monthlyResetAt),\n subscriptionExpiresAt: credits.subscriptionExpiresAt\n ? toISOString(credits.subscriptionExpiresAt)\n : null,\n createdAt: toISOString(credits.createdAt),\n updatedAt: toISOString(credits.updatedAt),\n };\n}\n\n/**\n * Convert any timestamp-like value to ISO string\n */\nfunction toISOString(value: unknown): string {\n if (!value) return new Date().toISOString();\n if (typeof value === \"string\") return value;\n if (value instanceof Date) return value.toISOString();\n // Handle Firestore Timestamp-like objects\n if (typeof value === \"object\" && \"toDate\" in value && typeof (value as { toDate: () => Date }).toDate === \"function\") {\n return (value as { toDate: () => Date }).toDate().toISOString();\n }\n return new Date().toISOString();\n}\n","/**\n * Shared utilities for credit repository implementations\n *\n * These are pure functions with no server-only dependencies,\n * making them safe to use in any repository implementation.\n */\n\n/**\n * Generate a unique ID\n * Uses timestamp + random string for uniqueness\n */\nexport function generateId(): string {\n return `${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;\n}\n\n/**\n * Convert date-like value to Date object\n * Handles: Date, string, Firestore Timestamp\n */\nexport function toDate(value: Date | string | unknown): Date {\n if (value instanceof Date) return value;\n if (typeof value === \"string\") return new Date(value);\n // Handle Firestore Timestamp\n if (typeof value === \"object\" && value !== null && \"toDate\" in value) {\n return (value as { toDate: () => Date }).toDate();\n }\n return new Date();\n}\n\n/**\n * Get the first day of next month\n * Used for calculating monthly reset dates\n */\nexport function getNextMonthlyReset(from: Date = new Date()): Date {\n const next = new Date(from);\n next.setMonth(next.getMonth() + 1);\n next.setDate(1);\n next.setHours(0, 0, 0, 0);\n return next;\n}\n","/**\n * In-Memory Credit Repository Implementation\n *\n * A database-agnostic implementation of ICreditRepository for testing and prototyping.\n * All data is stored in memory and lost when the process restarts.\n *\n * Usage:\n * - Unit tests without database dependency\n * - Local development and prototyping\n * - Reference implementation for custom repositories\n */\n\nimport type {\n PortableUserCredits,\n PortableReservation,\n PortableTransaction,\n PortableJournalEntry,\n PortableUsageLog,\n SubscriptionTier,\n ReservationStatus,\n MonthlyResetResult,\n SubscriptionExpiryResult,\n} from \"../../core/types.js\";\nimport type {\n ICreditRepository,\n CreateReservationInput,\n CreateTransactionInput,\n CreateUsageLogInput,\n CreateJournalEntryInput,\n UsageLogQuery,\n JournalEntryQuery,\n CreditBalanceUpdate,\n TierUpdateInput,\n} from \"../types.js\";\nimport { generateId, toDate, getNextMonthlyReset } from \"../utils.js\";\nimport { getConfig, getConfigMonthlyLimit } from \"../../config/index.js\";\n\n/**\n * In-Memory implementation of ICreditRepository\n *\n * Implements all repository methods using Map-based storage.\n * Useful for testing and as a reference implementation.\n */\nexport class InMemoryCreditRepository implements ICreditRepository {\n private users = new Map<string, PortableUserCredits>();\n private reservations = new Map<string, Map<string, PortableReservation>>();\n private transactions = new Map<string, PortableTransaction[]>();\n private usageLogs: PortableUsageLog[] = [];\n private journalEntries = new Map<string, PortableJournalEntry[]>();\n\n // ==================== User Credits ====================\n\n async getUserCredits(userId: string): Promise<PortableUserCredits | null> {\n return this.users.get(userId) ?? null;\n }\n\n async initializeUserCredits(\n userId: string,\n tier: SubscriptionTier,\n initialBalance: number\n ): Promise<PortableUserCredits> {\n const now = new Date().toISOString();\n const credits: PortableUserCredits = {\n userId,\n balance: initialBalance,\n bonusCredits: 0,\n reserved: 0,\n tier,\n monthlyLimit: getConfigMonthlyLimit(tier),\n monthlyUsed: 0,\n monthlyResetAt: getNextMonthlyReset().toISOString(),\n subscriptionExpiresAt: null,\n createdAt: now,\n updatedAt: now,\n };\n this.users.set(userId, credits);\n return credits;\n }\n\n async updateUserCredits(userId: string, updates: CreditBalanceUpdate): Promise<void> {\n const credits = this.users.get(userId);\n if (!credits) {\n throw new Error(`User ${userId} not found`);\n }\n\n const now = new Date().toISOString();\n\n // Apply absolute updates\n if (updates.balance !== undefined) credits.balance = updates.balance;\n if (updates.bonusCredits !== undefined) credits.bonusCredits = updates.bonusCredits;\n if (updates.reserved !== undefined) credits.reserved = updates.reserved;\n if (updates.tier !== undefined) credits.tier = updates.tier;\n if (updates.monthlyLimit !== undefined) credits.monthlyLimit = updates.monthlyLimit;\n if (updates.monthlyUsed !== undefined) credits.monthlyUsed = updates.monthlyUsed;\n if (updates.monthlyResetAt !== undefined) {\n credits.monthlyResetAt = updates.monthlyResetAt instanceof Date\n ? updates.monthlyResetAt.toISOString()\n : updates.monthlyResetAt;\n }\n if (updates.subscriptionExpiresAt !== undefined) {\n if (updates.subscriptionExpiresAt === null) {\n credits.subscriptionExpiresAt = null;\n } else {\n credits.subscriptionExpiresAt = updates.subscriptionExpiresAt instanceof Date\n ? updates.subscriptionExpiresAt.toISOString()\n : updates.subscriptionExpiresAt;\n }\n }\n\n // Apply increments\n if (updates.balanceIncrement !== undefined) {\n credits.balance += updates.balanceIncrement;\n }\n if (updates.bonusCreditsIncrement !== undefined) {\n credits.bonusCredits += updates.bonusCreditsIncrement;\n }\n if (updates.reservedIncrement !== undefined) {\n credits.reserved += updates.reservedIncrement;\n }\n if (updates.monthlyUsedIncrement !== undefined) {\n credits.monthlyUsed += updates.monthlyUsedIncrement;\n }\n\n credits.updatedAt = now;\n this.users.set(userId, credits);\n }\n\n async updateUserTier(userId: string, input: TierUpdateInput): Promise<void> {\n const credits = this.users.get(userId);\n if (!credits) {\n throw new Error(`User ${userId} not found`);\n }\n\n credits.tier = input.tier;\n credits.monthlyLimit = input.monthlyLimit;\n if (input.balance !== undefined) credits.balance = input.balance;\n if (input.monthlyUsed !== undefined) credits.monthlyUsed = input.monthlyUsed;\n if (input.subscriptionExpiresAt !== undefined) {\n if (input.subscriptionExpiresAt === null) {\n credits.subscriptionExpiresAt = null;\n } else {\n credits.subscriptionExpiresAt = input.subscriptionExpiresAt instanceof Date\n ? input.subscriptionExpiresAt.toISOString()\n : input.subscriptionExpiresAt;\n }\n }\n credits.updatedAt = new Date().toISOString();\n\n this.users.set(userId, credits);\n }\n\n // ==================== Reservations ====================\n\n async createReservation(input: CreateReservationInput): Promise<PortableReservation> {\n const now = new Date().toISOString();\n const reservation: PortableReservation = {\n id: generateId(),\n userId: input.userId,\n amount: input.amount,\n operationType: input.operationType,\n status: \"reserved\",\n createdAt: now,\n expiresAt: input.expiresAt.toISOString(),\n };\n\n if (!this.reservations.has(input.userId)) {\n this.reservations.set(input.userId, new Map());\n }\n this.reservations.get(input.userId)!.set(reservation.id, reservation);\n\n return reservation;\n }\n\n async getReservation(\n userId: string,\n reservationId: string\n ): Promise<PortableReservation | null> {\n const userReservations = this.reservations.get(userId);\n if (!userReservations) return null;\n return userReservations.get(reservationId) ?? null;\n }\n\n async updateReservationStatus(\n userId: string,\n reservationId: string,\n status: ReservationStatus,\n completedAt?: Date\n ): Promise<void> {\n const userReservations = this.reservations.get(userId);\n if (!userReservations) {\n throw new Error(`No reservations found for user ${userId}`);\n }\n\n const reservation = userReservations.get(reservationId);\n if (!reservation) {\n throw new Error(`Reservation ${reservationId} not found`);\n }\n\n reservation.status = status;\n if (completedAt) reservation.completedAt = completedAt.toISOString();\n\n userReservations.set(reservationId, reservation);\n }\n\n // ==================== Atomic Operations ====================\n\n async reserveCreditsAtomic(\n userId: string,\n amount: number,\n operationType: string,\n expiresAt: Date\n ): Promise<PortableReservation> {\n const credits = this.users.get(userId);\n if (!credits) {\n throw new Error(`User ${userId} not found`);\n }\n\n // Calculate available credits (balance + bonusCredits - reserved)\n const available = credits.balance + credits.bonusCredits - credits.reserved;\n if (available < amount) {\n throw new Error(\n `Insufficient credits. Available: ${available}, Required: ${amount}`\n );\n }\n\n // Create reservation\n const reservation = await this.createReservation({\n userId,\n amount,\n operationType,\n expiresAt,\n });\n\n // Update reserved amount\n credits.reserved += amount;\n credits.updatedAt = new Date().toISOString();\n this.users.set(userId, credits);\n\n return reservation;\n }\n\n async commitReservationAtomic(userId: string, reservationId: string): Promise<void> {\n const credits = this.users.get(userId);\n if (!credits) {\n throw new Error(`User ${userId} not found`);\n }\n\n const reservation = await this.getReservation(userId, reservationId);\n if (!reservation) {\n throw new Error(`Reservation ${reservationId} not found`);\n }\n\n // Idempotent: re-committing an already-committed reservation is a no-op\n // (retry-safe). Committing a released/expired one is a genuine conflict.\n if (reservation.status === \"committed\") {\n return;\n }\n\n if (reservation.status !== \"reserved\") {\n throw new Error(\n `Cannot commit reservation in ${reservation.status} state`\n );\n }\n\n const amount = reservation.amount;\n\n // Deduct from balance (bonus credits first logic can be added if needed)\n credits.balance -= amount;\n credits.reserved -= amount;\n credits.monthlyUsed += amount;\n credits.updatedAt = new Date().toISOString();\n this.users.set(userId, credits);\n\n // Update reservation status\n await this.updateReservationStatus(userId, reservationId, \"committed\", new Date());\n }\n\n async releaseReservationAtomic(userId: string, reservationId: string): Promise<void> {\n const credits = this.users.get(userId);\n if (!credits) {\n throw new Error(`User ${userId} not found`);\n }\n\n const reservation = await this.getReservation(userId, reservationId);\n if (!reservation) {\n throw new Error(`Reservation ${reservationId} not found`);\n }\n\n if (reservation.status !== \"reserved\") {\n // Already processed, no-op\n return;\n }\n\n // Release reserved credits\n credits.reserved -= reservation.amount;\n credits.updatedAt = new Date().toISOString();\n this.users.set(userId, credits);\n\n // Update reservation status\n await this.updateReservationStatus(userId, reservationId, \"released\", new Date());\n }\n\n async addCreditsAtomic(\n userId: string,\n amount: number,\n description: string,\n paymentRef?: string\n ): Promise<void> {\n const credits = this.users.get(userId);\n if (!credits) {\n throw new Error(`User ${userId} not found`);\n }\n\n const previousBalance = credits.bonusCredits;\n credits.bonusCredits += amount;\n credits.updatedAt = new Date().toISOString();\n this.users.set(userId, credits);\n\n // Create transaction\n await this.createTransaction({\n userId,\n type: \"purchase\",\n amount,\n description,\n paymentRef,\n previousBalance,\n newBalance: credits.bonusCredits,\n });\n }\n\n async deductCreditsAtomic(\n userId: string,\n amount: number\n ): Promise<{ previousBalance: number; newBalance: number }> {\n if (amount <= 0) {\n throw new Error(`deductCreditsAtomic amount must be positive (got ${amount})`);\n }\n\n const credits = this.users.get(userId);\n if (!credits) {\n throw new Error(`User credits not found for userId: ${userId}`);\n }\n\n const available = credits.balance + credits.bonusCredits - credits.reserved;\n if (available < amount) {\n throw new Error(\n `Insufficient credits. Available: ${available}, requested: ${amount}`\n );\n }\n\n // Drain balance first, then bonusCredits — same policy as commit.\n const balanceDeduction = Math.min(credits.balance, amount);\n const bonusDeduction = amount - balanceDeduction;\n\n const previousBalance = credits.balance + credits.bonusCredits;\n credits.balance -= balanceDeduction;\n credits.bonusCredits -= bonusDeduction;\n credits.updatedAt = new Date().toISOString();\n this.users.set(userId, credits);\n\n return { previousBalance, newBalance: previousBalance - amount };\n }\n\n // ==================== Transactions ====================\n\n async createTransaction(input: CreateTransactionInput): Promise<PortableTransaction> {\n const transaction: PortableTransaction = {\n id: generateId(),\n userId: input.userId,\n type: input.type,\n amount: input.amount,\n description: input.description,\n paymentRef: input.paymentRef,\n previousBalance: input.previousBalance,\n newBalance: input.newBalance,\n createdAt: new Date().toISOString(),\n };\n\n if (!this.transactions.has(input.userId)) {\n this.transactions.set(input.userId, []);\n }\n this.transactions.get(input.userId)!.push(transaction);\n\n return transaction;\n }\n\n async getTransactions(\n userId: string,\n limit = 50,\n offset = 0\n ): Promise<PortableTransaction[]> {\n const userTransactions = this.transactions.get(userId) ?? [];\n // Sort by createdAt descending (most recent first)\n const sorted = [...userTransactions].sort((a, b) => {\n const aDate = toDate(a.createdAt).getTime();\n const bDate = toDate(b.createdAt).getTime();\n return bDate - aDate;\n });\n return sorted.slice(offset, offset + limit);\n }\n\n // ==================== Usage Logs ====================\n\n async logUsage(input: CreateUsageLogInput): Promise<PortableUsageLog> {\n const log: PortableUsageLog = {\n id: generateId(),\n userId: input.userId,\n operationType: input.operationType,\n provider: input.provider,\n creditsUsed: input.creditsUsed,\n success: input.success,\n errorMessage: input.errorMessage,\n resourceId: input.resourceId,\n resourceType: input.resourceType,\n requestId: input.requestId,\n metadata: input.metadata,\n createdAt: new Date().toISOString(),\n };\n\n this.usageLogs.push(log);\n return log;\n }\n\n async getUsageLogs(query: UsageLogQuery): Promise<PortableUsageLog[]> {\n let results = [...this.usageLogs];\n\n // Apply filters\n if (query.userId) {\n results = results.filter((log) => log.userId === query.userId);\n }\n if (query.operationType) {\n results = results.filter((log) => log.operationType === query.operationType);\n }\n if (query.success !== undefined) {\n results = results.filter((log) => log.success === query.success);\n }\n if (query.startDate) {\n const startTime = query.startDate.getTime();\n results = results.filter(\n (log) => toDate(log.createdAt).getTime() >= startTime\n );\n }\n if (query.endDate) {\n const endTime = query.endDate.getTime();\n results = results.filter(\n (log) => toDate(log.createdAt).getTime() <= endTime\n );\n }\n\n // Sort by createdAt descending\n results.sort((a, b) => {\n return toDate(b.createdAt).getTime() - toDate(a.createdAt).getTime();\n });\n\n // Apply pagination\n const offset = query.offset ?? 0;\n const limit = query.limit ?? 50;\n return results.slice(offset, offset + limit);\n }\n\n async getUsageLogsCount(\n query: Omit<UsageLogQuery, \"limit\" | \"offset\">\n ): Promise<number> {\n const results = await this.getUsageLogs({ ...query, limit: Infinity, offset: 0 });\n return results.length;\n }\n\n // ==================== Journal Entries ====================\n\n async createJournalEntry(input: CreateJournalEntryInput): Promise<PortableJournalEntry> {\n const entry: PortableJournalEntry = {\n id: generateId(),\n userId: input.userId,\n entryType: input.entryType,\n amount: input.amount,\n balanceAfter: input.balanceAfter,\n source: input.source,\n referenceId: input.referenceId,\n referenceType: input.referenceType,\n description: input.description,\n metadata: input.metadata,\n createdAt: new Date().toISOString(),\n };\n\n if (!this.journalEntries.has(input.userId)) {\n this.journalEntries.set(input.userId, []);\n }\n this.journalEntries.get(input.userId)!.push(entry);\n\n return entry;\n }\n\n async getJournalEntries(query: JournalEntryQuery): Promise<PortableJournalEntry[]> {\n let results = this.journalEntries.get(query.userId) ?? [];\n\n // Apply filters\n if (query.source) {\n results = results.filter((entry) => entry.source === query.source);\n }\n if (query.referenceType) {\n results = results.filter((entry) => entry.referenceType === query.referenceType);\n }\n if (query.startDate) {\n const startTime = query.startDate.getTime();\n results = results.filter(\n (entry) => toDate(entry.createdAt).getTime() >= startTime\n );\n }\n if (query.endDate) {\n const endTime = query.endDate.getTime();\n results = results.filter(\n (entry) => toDate(entry.createdAt).getTime() <= endTime\n );\n }\n\n // Sort by createdAt descending\n results = [...results].sort((a, b) => {\n return toDate(b.createdAt).getTime() - toDate(a.createdAt).getTime();\n });\n\n // Apply pagination\n const offset = query.offset ?? 0;\n const limit = query.limit ?? 50;\n return results.slice(offset, offset + limit);\n }\n\n async getJournalEntriesCount(\n query: Omit<JournalEntryQuery, \"limit\" | \"offset\">\n ): Promise<number> {\n const results = await this.getJournalEntries({\n ...query,\n limit: Infinity,\n offset: 0,\n });\n return results.length;\n }\n\n // ==================== Cleanup Operations ====================\n\n async findAndExpireReservations(\n _batchSize = 100,\n _maxIterations = 100\n ): Promise<{\n expiredCount: number;\n creditsReleased: number;\n errors: string[];\n }> {\n const now = new Date();\n let expiredCount = 0;\n let creditsReleased = 0;\n const errors: string[] = [];\n\n for (const [userId, userReservations] of this.reservations) {\n for (const [reservationId, reservation] of userReservations) {\n if (\n reservation.status === \"reserved\" &&\n toDate(reservation.expiresAt).getTime() < now.getTime()\n ) {\n try {\n // Release the reservation\n await this.releaseReservationAtomic(userId, reservationId);\n // Mark as expired instead of released\n reservation.status = \"expired\";\n userReservations.set(reservationId, reservation);\n\n expiredCount++;\n creditsReleased += reservation.amount;\n } catch (error) {\n errors.push(\n `Failed to expire reservation ${reservationId}: ${error}`\n );\n }\n }\n }\n }\n\n return { expiredCount, creditsReleased, errors };\n }\n\n // ==================== Atomic Monthly Reset ====================\n\n async atomicMonthlyReset(\n userId: string,\n tier: SubscriptionTier,\n expectedResetAt: Date | string\n ): Promise<MonthlyResetResult> {\n const credits = this.users.get(userId);\n if (!credits) {\n throw new Error(`User ${userId} not found`);\n }\n\n // Optimistic locking: check if expectedResetAt matches current value\n const currentResetAt = toDate(credits.monthlyResetAt).getTime();\n const expected = toDate(expectedResetAt).getTime();\n\n if (currentResetAt !== expected) {\n // Another request already performed the reset\n return { wasReset: false, credits };\n }\n\n // Perform the reset\n const newBalance = getConfigMonthlyLimit(tier);\n const nextReset = getNextMonthlyReset();\n\n credits.balance = newBalance === Infinity ? credits.balance : newBalance;\n credits.monthlyUsed = 0;\n credits.monthlyResetAt = nextReset.toISOString();\n credits.updatedAt = new Date().toISOString();\n\n this.users.set(userId, credits);\n\n return { wasReset: true, credits };\n }\n\n // ==================== Subscription Expiry ====================\n\n async checkAndHandleSubscriptionExpiry(\n userId: string,\n gracePeriodDays = 3\n ): Promise<SubscriptionExpiryResult> {\n const credits = this.users.get(userId);\n if (!credits) {\n throw new Error(`User ${userId} not found`);\n }\n\n // Free tier doesn't expire\n if (credits.tier === \"free\" || !credits.subscriptionExpiresAt) {\n return {\n wasDowngraded: false,\n inGracePeriod: false,\n graceDaysRemaining: 0,\n credits,\n };\n }\n\n const now = new Date();\n const expiresAt = toDate(credits.subscriptionExpiresAt);\n const daysSinceExpiry =\n (now.getTime() - expiresAt.getTime()) / (1000 * 60 * 60 * 24);\n\n if (daysSinceExpiry <= 0) {\n // Not expired yet\n return {\n wasDowngraded: false,\n inGracePeriod: false,\n graceDaysRemaining: 0,\n credits,\n };\n }\n\n if (daysSinceExpiry <= gracePeriodDays) {\n // In grace period\n return {\n wasDowngraded: false,\n inGracePeriod: true,\n graceDaysRemaining: Math.ceil(gracePeriodDays - daysSinceExpiry),\n credits,\n };\n }\n\n // Grace period expired - downgrade to free\n const config = getConfig();\n const freeTierConfig = config.tierConfigs.free!;\n\n credits.tier = \"free\";\n credits.monthlyLimit = freeTierConfig.monthlyCredits;\n credits.balance = Math.min(credits.balance, freeTierConfig.monthlyCredits);\n credits.subscriptionExpiresAt = null;\n credits.updatedAt = new Date().toISOString();\n\n this.users.set(userId, credits);\n\n return {\n wasDowngraded: true,\n inGracePeriod: false,\n graceDaysRemaining: 0,\n credits,\n };\n }\n\n // ==================== Testing Utilities ====================\n\n /**\n * Clear all data (useful for testing)\n */\n clear(): void {\n this.users.clear();\n this.reservations.clear();\n this.transactions.clear();\n this.usageLogs = [];\n this.journalEntries.clear();\n }\n\n /**\n * Get all users (useful for testing/debugging)\n */\n getAllUsers(): PortableUserCredits[] {\n return Array.from(this.users.values());\n }\n\n /**\n * Get all reservations for a user (useful for testing)\n */\n getAllReservations(userId: string): PortableReservation[] {\n const userReservations = this.reservations.get(userId);\n if (!userReservations) return [];\n return Array.from(userReservations.values());\n }\n}\n\n/**\n * Create a new in-memory repository instance\n * Each call creates a fresh, isolated instance\n */\nexport function createInMemoryCreditRepository(): InMemoryCreditRepository {\n return new InMemoryCreditRepository();\n}\n","import type { ICreditsAuthProvider, CreditsUser } from \"./types.js\";\n\n/**\n * Timing-safe string comparison to prevent timing attacks\n */\nfunction timingSafeEqual(a: string, b: string): boolean {\n if (!a || !b) {\n return false;\n }\n\n // Pad strings to same length to prevent length-based timing attacks\n const maxLength = Math.max(a.length, b.length);\n const paddedA = a.padEnd(maxLength);\n const paddedB = b.padEnd(maxLength);\n\n let result = 0;\n for (let i = 0; i < maxLength; i++) {\n result |= paddedA.charCodeAt(i) ^ paddedB.charCodeAt(i);\n }\n\n // Also check that lengths were originally equal\n return result === 0 && a.length === b.length;\n}\n\n/**\n * Verify a Bearer token from an Authorization header\n *\n * @param header - The Authorization header value (e.g., \"Bearer secret123\")\n * @param secret - The expected secret value\n * @returns True if token is valid\n */\nexport function verifyBearerToken(header: string | null, secret: string): boolean {\n if (!header || !secret) {\n return false;\n }\n\n // Extract token from \"Bearer {token}\" format\n const token = header.replace(/^Bearer\\s+/i, \"\");\n\n // Use timing-safe comparison\n return timingSafeEqual(token, secret);\n}\n\n/**\n * API Key auth provider for admin routes\n *\n * Uses Bearer token authentication for external API access\n * The API key is stored in CREDITS_ADMIN_API_KEY environment variable\n */\nexport class ApiKeyCreditsProvider implements ICreditsAuthProvider {\n private readonly apiKey: string | undefined;\n private isValidated = false;\n\n constructor(authHeader?: string | null, apiKey?: string) {\n this.apiKey = apiKey ?? process.env.CREDITS_ADMIN_API_KEY;\n\n // Validate the provided auth header using timing-safe comparison\n if (authHeader && this.apiKey) {\n this.isValidated = verifyBearerToken(authHeader, this.apiKey);\n }\n }\n\n async getCurrentUser(): Promise<CreditsUser | null> {\n if (!this.isValidated) {\n return null;\n }\n\n // API key auth returns a special \"admin\" user\n return {\n id: \"api-admin\",\n email: \"api@admin.internal\",\n name: \"API Admin\",\n };\n }\n\n async verifyAdminAccess(): Promise<boolean> {\n return this.isValidated;\n }\n}\n\n/**\n * Verify an API key from a request header\n * @param authHeader - Authorization header value\n * @param apiKey - Optional API key to use (defaults to CREDITS_ADMIN_API_KEY env var)\n * @returns True if API key is valid\n */\nexport function verifyAdminApiKey(authHeader: string | null, apiKey?: string): boolean {\n if (!authHeader) {\n return false;\n }\n\n const expectedKey = apiKey ?? process.env.CREDITS_ADMIN_API_KEY;\n if (!expectedKey) {\n console.warn(\"CREDITS_ADMIN_API_KEY not configured\");\n return false;\n }\n\n // Use timing-safe comparison to prevent timing attacks\n return verifyBearerToken(authHeader, expectedKey);\n}\n\n/**\n * Create an API key auth provider from a request\n * @param request - Request object with headers\n * @param apiKey - Optional API key to use\n * @returns API key auth provider\n */\nexport function createApiKeyProvider(request: Request, apiKey?: string): ApiKeyCreditsProvider {\n const authHeader = request.headers.get(\"Authorization\");\n return new ApiKeyCreditsProvider(authHeader, apiKey);\n}\n","import type {\n PortableUserCredits,\n PortableReservation,\n CreditCheckResult,\n CreditOperationType,\n SubscriptionTier,\n PortableUsageLog,\n PortableJournalEntry,\n UsageHistoryEntry,\n UsageHistoryResponse,\n} from \"../core/types.js\";\nimport { toDate } from \"../core/types.js\";\nimport type { ICreditRepository, CreateUsageLogInput, JournalEntryQuery } from \"../repository/types.js\";\nimport { toClientUserCredits } from \"../repository/types.js\";\nimport { getConfig, getConfigMonthlyLimit, getOperationLabel } from \"../config/index.js\";\n\n/**\n * Check if a date is past the monthly reset date\n */\nfunction isPastMonthlyReset(resetAt: unknown): boolean {\n if (!resetAt) return false;\n const resetDate = toDate(resetAt);\n return new Date() >= resetDate;\n}\n\n/**\n * Notification callback type for low balance notifications\n */\nexport type LowBalanceNotificationCallback = (userId: string, balance: number) => Promise<void>;\n\n/**\n * Notification callback type for subscription expired notifications\n */\nexport type SubscriptionExpiredNotificationCallback = (userId: string, wasDowngraded: boolean) => Promise<void>;\n\n/**\n * Options for reserving credits.\n */\nexport interface ReserveCreditsOptions {\n /**\n * Time-to-live for the reservation, in milliseconds, before the cleanup\n * sweep may expire it. Defaults to `config.reservationExpiryMs` (suitable\n * for synchronous, in-request operations). Long-running async jobs\n * (e.g. background media generation) should pass a TTL matching their\n * own lifecycle so the reservation is not expired while the job is still\n * legitimately in flight.\n */\n ttlMs?: number;\n}\n\n/**\n * Credits service with dependency injection for repository\n *\n * Provides business logic for credit operations, delegating\n * database operations to the injected repository.\n */\nexport class CreditsService {\n private lowBalanceCallback?: LowBalanceNotificationCallback;\n private subscriptionExpiredCallback?: SubscriptionExpiredNotificationCallback;\n\n constructor(private readonly repository: ICreditRepository) {}\n\n /**\n * Set callback for low balance notifications\n */\n setLowBalanceCallback(callback: LowBalanceNotificationCallback): void {\n this.lowBalanceCallback = callback;\n }\n\n /**\n * Set callback for subscription expired notifications\n */\n setSubscriptionExpiredCallback(callback: SubscriptionExpiredNotificationCallback): void {\n this.subscriptionExpiredCallback = callback;\n }\n\n /**\n * Get user credits, performing monthly reset and subscription expiry checks if needed\n *\n * This method uses atomic operations to prevent race conditions:\n * 1. Checks subscription expiry with grace period\n * 2. Atomically performs monthly reset if needed (with optimistic locking)\n *\n * @param userId - User ID\n * @returns User credits or null if not found\n */\n async getUserCredits(userId: string): Promise<PortableUserCredits | null> {\n let data = await this.repository.getUserCredits(userId);\n\n if (!data) {\n return null;\n }\n\n // Step 1: Check subscription expiry (for non-free tiers)\n if (data.tier !== \"free\" && data.subscriptionExpiresAt) {\n const expiryResult = await this.repository.checkAndHandleSubscriptionExpiry(\n userId,\n getConfig().subscriptionGracePeriodDays\n );\n\n if (expiryResult.wasDowngraded) {\n // Create journal entry for downgrade\n await this.repository.createJournalEntry({\n userId,\n entryType: \"debit\",\n amount: 0, // No credits deducted, just tier change\n balanceAfter: expiryResult.credits.balance,\n source: \"subscription_downgrade\",\n referenceId: `downgrade-${Date.now()}`,\n referenceType: \"subscription\",\n description: `Subscription expired. Downgraded from ${data.tier} to free tier.`,\n metadata: {\n previousTier: data.tier,\n previousBalance: data.balance,\n newBalance: expiryResult.credits.balance,\n },\n });\n\n // Trigger subscription expired notification (non-blocking)\n if (this.subscriptionExpiredCallback) {\n this.subscriptionExpiredCallback(userId, true).catch((error) => {\n console.error(\"[Credits] Failed to send subscription expired notification:\", error);\n });\n }\n }\n\n // Use the potentially updated credits\n data = expiryResult.credits;\n }\n\n // Step 2: Check if monthly reset is needed (use atomic operation)\n if (isPastMonthlyReset(data.monthlyResetAt)) {\n // Convert monthlyResetAt to a compatible type (Date or string)\n const expectedResetAt = toDate(data.monthlyResetAt);\n const resetResult = await this.repository.atomicMonthlyReset(\n userId,\n data.tier,\n expectedResetAt\n );\n\n if (resetResult.wasReset) {\n // Create journal entry for monthly reset\n const balanceChange = resetResult.credits.balance - data.balance;\n if (balanceChange !== 0) {\n await this.repository.createJournalEntry({\n userId,\n entryType: balanceChange > 0 ? \"credit\" : \"debit\",\n amount: Math.abs(balanceChange),\n balanceAfter: resetResult.credits.balance,\n source: \"monthly_reset\",\n referenceId: `reset-${Date.now()}`,\n referenceType: \"reset\",\n description: `Monthly credit reset for ${data.tier} tier.`,\n metadata: {\n tier: data.tier,\n previousBalance: data.balance,\n newBalance: resetResult.credits.balance,\n },\n });\n }\n }\n\n // Use the potentially updated credits\n data = resetResult.credits;\n }\n\n return toClientUserCredits(data);\n }\n\n /**\n * Initialize credits for a new user with free tier\n * @param userId - User ID\n * @returns Initialized user credits\n */\n async initializeUserCredits(userId: string): Promise<PortableUserCredits> {\n const credits = await this.repository.initializeUserCredits(\n userId,\n \"free\",\n getConfig().defaultFreeCredits\n );\n return toClientUserCredits(credits);\n }\n\n /**\n * Get or create user credits\n * Initializes with free tier if not exists\n * @param userId - User ID\n * @returns User credits\n */\n async getOrCreateUserCredits(userId: string): Promise<PortableUserCredits> {\n const existing = await this.getUserCredits(userId);\n if (existing) {\n return existing;\n }\n return this.initializeUserCredits(userId);\n }\n\n /**\n * Check if user has sufficient credits for an operation\n * @param userId - User ID\n * @param requiredCredits - Credits required\n * @returns Credit check result\n */\n async checkCredits(userId: string, requiredCredits: number): Promise<CreditCheckResult> {\n const credits = await this.getOrCreateUserCredits(userId);\n\n // Available = balance + bonusCredits - reserved\n const totalBalance = credits.balance + credits.bonusCredits;\n const available = totalBalance - credits.reserved;\n const hasCredits = available >= requiredCredits;\n\n return {\n hasCredits,\n balance: totalBalance,\n required: requiredCredits,\n shortfall: hasCredits ? 0 : requiredCredits - available,\n };\n }\n\n /**\n * Reserve credits for an operation (phase 1 of two-phase commit)\n * Creates a reservation and locks the credits\n * @param userId - User ID\n * @param amount - Credits to reserve\n * @param operationType - Operation type for tracking\n * @param options - Optional settings (e.g. a custom `ttlMs` for long-running async jobs)\n * @returns Reservation object\n * @throws Error if insufficient credits\n */\n async reserveCredits(\n userId: string,\n amount: number,\n operationType: CreditOperationType,\n options?: ReserveCreditsOptions\n ): Promise<PortableReservation> {\n const ttlMs = options?.ttlMs ?? getConfig().reservationExpiryMs;\n const expiresAt = new Date(Date.now() + ttlMs);\n return this.repository.reserveCreditsAtomic(userId, amount, operationType, expiresAt);\n }\n\n /**\n * Commit a reservation (phase 2 of two-phase commit - success)\n * Deducts credits and marks reservation as committed\n * Also triggers low balance notifications if balance drops below threshold\n */\n async commitCredits(userId: string, reservationId: string): Promise<void> {\n // Get the reservation to know the amount\n const reservation = await this.repository.getReservation(userId, reservationId);\n if (!reservation) {\n throw new Error(`Reservation ${reservationId} not found`);\n }\n\n // Idempotent: a re-delivered commit for an already-committed reservation\n // is a no-op. The balance + journal were applied by the first commit; the\n // atomic layer also guards the balance, this just avoids a duplicate\n // journal entry on the happy retry path.\n if (reservation.status === \"committed\") {\n return;\n }\n\n // Commit the reservation atomically\n await this.repository.commitReservationAtomic(userId, reservationId);\n\n // Create journal entry\n const credits = await this.repository.getUserCredits(userId);\n if (credits) {\n await this.repository.createJournalEntry({\n userId,\n entryType: \"debit\",\n amount: reservation.amount,\n balanceAfter: credits.balance,\n source: \"operation_commit\",\n referenceId: reservationId,\n referenceType: \"reservation\",\n description: `Committed ${reservation.amount} credits for ${getOperationLabel(reservation.operationType)}`,\n metadata: {\n operationType: reservation.operationType,\n },\n });\n\n // Trigger low balance notification (non-blocking)\n if (this.lowBalanceCallback) {\n this.lowBalanceCallback(userId, credits.balance).catch((error) => {\n console.error(\"[Credits] Failed to send low balance notification:\", error);\n });\n }\n }\n }\n\n /**\n * Release a reservation (phase 2 of two-phase commit - failure)\n * Returns reserved credits and marks reservation as released\n */\n async releaseCredits(userId: string, reservationId: string): Promise<void> {\n // Get the reservation to check its state\n const reservation = await this.repository.getReservation(userId, reservationId);\n\n // Release the reservation atomically\n await this.repository.releaseReservationAtomic(userId, reservationId);\n\n // Create journal entry only if reservation was in reserved state\n if (reservation?.status === \"reserved\") {\n const credits = await this.repository.getUserCredits(userId);\n if (credits) {\n await this.repository.createJournalEntry({\n userId,\n entryType: \"credit\",\n amount: 0, // No actual credits returned (they were reserved, not spent)\n balanceAfter: credits.balance,\n source: \"operation_release\",\n referenceId: reservationId,\n referenceType: \"reservation\",\n description: `Released ${reservation.amount} reserved credits for ${getOperationLabel(reservation.operationType)}`,\n metadata: {\n operationType: reservation.operationType,\n amount: reservation.amount,\n },\n });\n }\n }\n }\n\n /**\n * Log usage for audit trail\n * @param log - Usage log data\n */\n async logUsage(log: Omit<PortableUsageLog, \"id\" | \"createdAt\">): Promise<void> {\n await this.repository.logUsage(log as CreateUsageLogInput);\n }\n\n /**\n * Add credits to user account (for purchases, bonuses, etc.)\n * @param userId - User ID\n * @param amount - Credits to add\n * @param description - Transaction description\n * @param paymentRef - Optional payment reference\n */\n async addCredits(\n userId: string,\n amount: number,\n description: string,\n paymentRef?: string\n ): Promise<void> {\n return this.repository.addCreditsAtomic(userId, amount, description, paymentRef);\n }\n\n /**\n * Update user subscription tier\n * @param userId - User ID\n * @param tier - New subscription tier\n * @param expiresAt - Subscription expiry date (optional)\n */\n async updateTier(\n userId: string,\n tier: SubscriptionTier,\n expiresAt?: Date\n ): Promise<void> {\n const monthlyLimit = getConfigMonthlyLimit(tier);\n\n await this.repository.updateUserTier(userId, {\n tier,\n monthlyLimit: monthlyLimit === Infinity ? 0 : monthlyLimit,\n // Reset balance to new tier limit if upgrading\n balance: tier !== \"free\"\n ? (monthlyLimit === Infinity ? 999999 : monthlyLimit)\n : undefined,\n monthlyUsed: tier !== \"free\" ? 0 : undefined,\n subscriptionExpiresAt: expiresAt ? expiresAt.toISOString() : null,\n });\n }\n\n /**\n * Get usage logs with optional filtering\n * @param userId - Optional user ID filter\n * @param limit - Max results\n * @param offset - Skip results\n * @returns List of usage logs\n */\n async getUsageLogs(\n userId?: string,\n limit = 50,\n offset = 0\n ): Promise<PortableUsageLog[]> {\n return this.repository.getUsageLogs({ userId, limit, offset });\n }\n\n /**\n * Get user-friendly usage history\n * Combines journal entries into a user-facing format\n *\n * @param userId - User ID\n * @param limit - Max results per page\n * @param offset - Skip results for pagination\n * @returns Paginated usage history response\n */\n async getUsageHistory(\n userId: string,\n limit = 20,\n offset = 0\n ): Promise<UsageHistoryResponse> {\n // Get journal entries\n const [entries, total] = await Promise.all([\n this.repository.getJournalEntries({ userId, limit, offset }),\n this.repository.getJournalEntriesCount({ userId }),\n ]);\n\n // Convert journal entries to user-friendly format\n const historyEntries: UsageHistoryEntry[] = entries.map((entry) => {\n const type = this.mapSourceToHistoryType(entry.source);\n const creditsChange = entry.entryType === \"credit\" ? entry.amount : -entry.amount;\n\n return {\n id: entry.id,\n type,\n creditsChange,\n balanceAfter: entry.balanceAfter,\n description: entry.description,\n createdAt: typeof entry.createdAt === \"string\"\n ? entry.createdAt\n : toDate(entry.createdAt).toISOString(),\n };\n });\n\n return {\n entries: historyEntries,\n pagination: {\n total,\n limit,\n offset,\n hasMore: offset + entries.length < total,\n },\n };\n }\n\n /**\n * Map journal entry source to user-friendly history type\n */\n private mapSourceToHistoryType(\n source: PortableJournalEntry[\"source\"]\n ): UsageHistoryEntry[\"type\"] {\n switch (source) {\n case \"operation_commit\":\n case \"reservation_expired\":\n return \"usage\";\n case \"purchase\":\n return \"purchase\";\n case \"subscription_grant\":\n case \"subscription_upgrade\":\n case \"bonus\":\n return \"bonus\";\n case \"monthly_reset\":\n return \"reset\";\n case \"refund\":\n case \"operation_release\":\n return \"refund\";\n case \"admin_adjustment\":\n case \"subscription_downgrade\":\n case \"expiry\":\n default:\n return \"adjustment\";\n }\n }\n\n /**\n * Get journal entries directly (for admin or debugging)\n * @param query - Journal entry query parameters\n * @returns List of journal entries\n */\n async getJournalEntries(query: JournalEntryQuery): Promise<PortableJournalEntry[]> {\n return this.repository.getJournalEntries(query);\n }\n\n /**\n * Get the underlying repository (for advanced use cases)\n */\n getRepository(): ICreditRepository {\n return this.repository;\n }\n}\n\n/**\n * Create a credits service with a repository\n */\nexport function createCreditsService(repository: ICreditRepository): CreditsService {\n return new CreditsService(repository);\n}\n","/**\n * Generic credits adapter - framework agnostic\n *\n * This adapter uses standard JavaScript APIs and works in any\n * JavaScript environment (Node.js, browser, etc.).\n */\n\nimport type { PortableReservation, WithCreditsOptions } from \"../core/types.js\";\nimport type { ActionResult, CreditsAdapter, CreditsAdapterConfig, CreditActionHandler } from \"./types.js\";\nimport {\n commitReservationWithJournal,\n releaseReservationWithJournal,\n reserveCreditsForOperation,\n} from \"../core/operations.js\";\n\n/**\n * Generate a unique request ID\n */\nfunction generateRequestId(): string {\n const timestamp = Date.now();\n const randomPart = Math.random().toString(36).substring(2, 14);\n return `req_${timestamp}_${randomPart}`;\n}\n\n/**\n * Create a generic credits adapter\n *\n * This adapter:\n * - Authenticates users via the provided auth provider\n * - Reserves credits before action execution\n * - Commits credits on success, releases on failure\n * - Logs usage asynchronously via the deferred executor\n */\nexport function createGenericAdapter(config: CreditsAdapterConfig): CreditsAdapter {\n const { repository, authProvider, deferred, operationCosts } = config;\n\n function getOperationCost(operationType: string): number {\n const cost = operationCosts[operationType];\n if (cost === undefined) {\n throw new Error(\n `Unknown operation type: \"${operationType}\". ` +\n `Configured types: ${Object.keys(operationCosts).join(\", \")}`\n );\n }\n return cost;\n }\n\n function logUsage(params: {\n userId: string;\n operationType: string;\n creditsUsed: number;\n success: boolean;\n errorMessage?: string;\n resourceId?: string;\n resourceType?: string;\n requestId: string;\n }): void {\n deferred.defer(async () => {\n await repository.logUsage({\n userId: params.userId,\n operationType: params.operationType,\n provider: \"gemini\",\n creditsUsed: params.creditsUsed,\n success: params.success,\n errorMessage: params.errorMessage,\n resourceId: params.resourceId,\n resourceType: params.resourceType,\n requestId: params.requestId,\n });\n });\n }\n\n return {\n withCredits<TInput, TOutput>(\n options: WithCreditsOptions,\n handler: CreditActionHandler<TInput, TOutput>\n ): (data: TInput) => Promise<ActionResult<TOutput>> {\n return async (data: TInput): Promise<ActionResult<TOutput>> => {\n const requestId = generateRequestId();\n\n // Authenticate\n const user = await authProvider.getCurrentUser();\n if (!user?.id) {\n return { success: false, error: \"Authentication required\" };\n }\n\n // Handle preview mode - skip credit handling entirely\n if (isPreviewMode(data)) {\n const dummyReservation = createDummyReservation(user.id, options.operationType);\n return handler(user, data, dummyReservation);\n }\n\n // Calculate cost and reserve credits\n let reservation: PortableReservation;\n try {\n const cost = options.customCost ?? getOperationCost(options.operationType);\n reservation = await reserveCreditsForOperation(\n repository,\n user.id,\n cost,\n options.operationType\n );\n } catch (error) {\n const message = error instanceof Error ? error.message : \"Failed to reserve credits\";\n logUsage({\n userId: user.id,\n operationType: options.operationType,\n creditsUsed: 0,\n success: false,\n errorMessage: message,\n resourceId: options.resourceId,\n resourceType: options.resourceType,\n requestId,\n });\n return { success: false, error: message };\n }\n\n // Execute action\n try {\n const result = await handler(user, data, reservation);\n\n if (result.success) {\n await commitReservationWithJournal(repository, user.id, reservation.id);\n logUsage({\n userId: user.id,\n operationType: options.operationType,\n creditsUsed: reservation.amount,\n success: true,\n resourceId: options.resourceId,\n resourceType: options.resourceType,\n requestId,\n });\n } else {\n await releaseReservationWithJournal(repository, user.id, reservation.id);\n logUsage({\n userId: user.id,\n operationType: options.operationType,\n creditsUsed: 0,\n success: false,\n errorMessage: result.error,\n resourceId: options.resourceId,\n resourceType: options.resourceType,\n requestId,\n });\n }\n\n return result;\n } catch (error) {\n const message = error instanceof Error ? error.message : \"An unexpected error occurred\";\n await releaseReservationWithJournal(repository, user.id, reservation.id);\n logUsage({\n userId: user.id,\n operationType: options.operationType,\n creditsUsed: 0,\n success: false,\n errorMessage: message,\n resourceId: options.resourceId,\n resourceType: options.resourceType,\n requestId,\n });\n console.error(\"Action error:\", error);\n return { success: false, error: message };\n }\n };\n },\n };\n}\n\nfunction isPreviewMode(data: unknown): boolean {\n return (\n typeof data === \"object\" &&\n data !== null &&\n \"preview\" in data &&\n (data as Record<string, unknown>).preview === true\n );\n}\n\nfunction createDummyReservation(userId: string, operationType: string): PortableReservation {\n return {\n id: \"preview-mode\",\n userId,\n amount: 0,\n operationType,\n status: \"released\",\n createdAt: new Date().toISOString(),\n expiresAt: new Date().toISOString(),\n };\n}\n","import { getConfig } from \"../config/index.js\";\n\n/**\n * Notification event types\n */\nexport type CreditNotificationEvent =\n | {\n type: \"low_balance\";\n userId: string;\n balance: number;\n threshold: number;\n }\n | {\n type: \"balance_depleted\";\n userId: string;\n }\n | {\n type: \"subscription_expiring\";\n userId: string;\n expiresAt: Date;\n daysRemaining: number;\n }\n | {\n type: \"subscription_expired\";\n userId: string;\n wasDowngraded: boolean;\n };\n\n/**\n * Notification handler interface\n * Implementations can send emails, push notifications, etc.\n */\nexport interface ICreditNotificationHandler {\n /**\n * Handle a notification event\n * @param event - The notification event\n */\n handleNotification(event: CreditNotificationEvent): Promise<void>;\n}\n\n/**\n * Notification cooldown tracker\n * Prevents spam by tracking last notification time per user per event type\n */\ninterface NotificationCooldownState {\n lastLowBalanceNotification?: Date;\n lastDepletedNotification?: Date;\n lastExpiringNotification?: Date;\n}\n\n/**\n * In-memory cooldown tracker\n * In production, this should be stored in a persistent store\n */\nconst cooldownState = new Map<string, NotificationCooldownState>();\n\n/**\n * Check if a notification is allowed (not in cooldown)\n */\nfunction isNotificationAllowed(\n userId: string,\n eventType: \"lowBalance\" | \"depleted\" | \"expiring\"\n): boolean {\n const state = cooldownState.get(userId);\n if (!state) return true;\n\n const now = Date.now();\n let lastNotification: Date | undefined;\n\n switch (eventType) {\n case \"lowBalance\":\n lastNotification = state.lastLowBalanceNotification;\n break;\n case \"depleted\":\n lastNotification = state.lastDepletedNotification;\n break;\n case \"expiring\":\n lastNotification = state.lastExpiringNotification;\n break;\n }\n\n if (!lastNotification) return true;\n return now - lastNotification.getTime() > getConfig().lowBalanceNotificationCooldownHours * 60 * 60 * 1000;\n}\n\n/**\n * Record that a notification was sent\n */\nfunction recordNotification(\n userId: string,\n eventType: \"lowBalance\" | \"depleted\" | \"expiring\"\n): void {\n const state = cooldownState.get(userId) || {};\n const now = new Date();\n\n switch (eventType) {\n case \"lowBalance\":\n state.lastLowBalanceNotification = now;\n break;\n case \"depleted\":\n state.lastDepletedNotification = now;\n break;\n case \"expiring\":\n state.lastExpiringNotification = now;\n break;\n }\n\n cooldownState.set(userId, state);\n}\n\n/**\n * Default notification handlers registry\n * Handlers can be registered at application startup\n */\nconst notificationHandlers: ICreditNotificationHandler[] = [];\n\n/**\n * Register a notification handler\n */\nexport function registerNotificationHandler(\n handler: ICreditNotificationHandler\n): void {\n notificationHandlers.push(handler);\n}\n\n/**\n * Clear all notification handlers (for testing)\n */\nexport function clearNotificationHandlers(): void {\n notificationHandlers.length = 0;\n}\n\n/**\n * Clear cooldown state (for testing)\n */\nexport function clearCooldownState(): void {\n cooldownState.clear();\n}\n\n/**\n * Dispatch a notification event to all handlers\n * Runs asynchronously without blocking\n */\nasync function dispatchNotification(\n event: CreditNotificationEvent\n): Promise<void> {\n if (notificationHandlers.length === 0) {\n // No handlers registered - log for debugging\n console.debug(`[Credits] No notification handlers registered for: ${event.type}`);\n return;\n }\n\n // Run handlers in parallel\n await Promise.allSettled(\n notificationHandlers.map((handler) =>\n handler.handleNotification(event).catch((error) => {\n console.error(`[Credits] Notification handler error:`, error);\n })\n )\n );\n}\n\n/**\n * Check and trigger low balance notification if needed\n * Call this after commit operations\n *\n * @param userId - User ID\n * @param newBalance - Balance after the operation\n * @param threshold - Custom threshold (defaults to 10)\n */\nexport async function checkAndNotifyLowBalance(\n userId: string,\n newBalance: number,\n threshold = getConfig().lowBalanceThreshold\n): Promise<void> {\n // Check if balance is depleted\n if (newBalance <= 0 && isNotificationAllowed(userId, \"depleted\")) {\n recordNotification(userId, \"depleted\");\n await dispatchNotification({\n type: \"balance_depleted\",\n userId,\n });\n return; // Don't also send low balance notification\n }\n\n // Check if balance is below threshold\n if (newBalance > 0 && newBalance <= threshold && isNotificationAllowed(userId, \"lowBalance\")) {\n recordNotification(userId, \"lowBalance\");\n await dispatchNotification({\n type: \"low_balance\",\n userId,\n balance: newBalance,\n threshold,\n });\n }\n}\n\n/**\n * Trigger subscription expiring notification\n *\n * @param userId - User ID\n * @param expiresAt - When subscription expires\n * @param daysRemaining - Days until expiry\n */\nexport async function notifySubscriptionExpiring(\n userId: string,\n expiresAt: Date,\n daysRemaining: number\n): Promise<void> {\n if (!isNotificationAllowed(userId, \"expiring\")) {\n return;\n }\n\n recordNotification(userId, \"expiring\");\n await dispatchNotification({\n type: \"subscription_expiring\",\n userId,\n expiresAt,\n daysRemaining,\n });\n}\n\n/**\n * Trigger subscription expired notification\n *\n * @param userId - User ID\n * @param wasDowngraded - Whether the user was downgraded to free tier\n */\nexport async function notifySubscriptionExpired(\n userId: string,\n wasDowngraded: boolean\n): Promise<void> {\n await dispatchNotification({\n type: \"subscription_expired\",\n userId,\n wasDowngraded,\n });\n}\n\n/**\n * Console notification handler for development/testing\n * Logs notifications to console\n */\nexport class ConsoleNotificationHandler implements ICreditNotificationHandler {\n async handleNotification(event: CreditNotificationEvent): Promise<void> {\n switch (event.type) {\n case \"low_balance\":\n console.log(\n `[Credits Notification] Low balance alert for user ${event.userId}: ` +\n `${event.balance} credits remaining (threshold: ${event.threshold})`\n );\n break;\n case \"balance_depleted\":\n console.log(\n `[Credits Notification] Balance depleted for user ${event.userId}`\n );\n break;\n case \"subscription_expiring\":\n console.log(\n `[Credits Notification] Subscription expiring for user ${event.userId}: ` +\n `${event.daysRemaining} days remaining`\n );\n break;\n case \"subscription_expired\":\n console.log(\n `[Credits Notification] Subscription expired for user ${event.userId}: ` +\n `downgraded=${event.wasDowngraded}`\n );\n break;\n }\n }\n}\n","/**\n * SDK types for the credits system\n *\n * These types are used by the client SDKs for REST API consumption.\n */\n\n// Re-export core types for SDK consumers\nexport type {\n PortableUserCredits as UserCredits,\n PortableReservation as CreditReservation,\n CreditCheckResult,\n SubscriptionTier,\n} from \"../core/types.js\";\n\n/**\n * Configuration for CreditsClient\n */\nexport interface CreditsClientConfig {\n /** Base URL of the credits API (e.g., \"https://api.example.com\") */\n baseUrl: string;\n /** Function to get the auth token for session-based auth */\n getAuthToken?: () => Promise<string>;\n /** Optional custom fetch function for testing */\n fetch?: typeof fetch;\n}\n\n/**\n * Configuration for AdminCreditsClient\n */\nexport interface AdminCreditsClientConfig {\n /** Base URL of the credits API */\n baseUrl: string;\n /** Admin API key */\n apiKey: string;\n /** Optional custom fetch function for testing */\n fetch?: typeof fetch;\n}\n\n/**\n * Result of a credit reservation\n */\nexport interface ReservationResult {\n /** The reservation ID */\n reservationId: string;\n /** Amount of credits reserved */\n amount: number;\n /** When the reservation expires (ISO 8601) */\n expiresAt: string;\n}\n\n/**\n * Pagination options for API requests\n */\nexport interface PaginationOptions {\n /** Page number (1-indexed) */\n page?: number;\n /** Number of items per page */\n limit?: number;\n}\n\n/**\n * Usage history entry\n */\nexport interface UsageHistoryEntry {\n /** Unique ID of the usage log */\n id: string;\n /** Type of operation */\n operationType: string;\n /** AI provider used */\n provider: string;\n /** Credits consumed */\n creditsUsed: number;\n /** Whether the operation was successful */\n success: boolean;\n /** Error message if unsuccessful */\n errorMessage?: string;\n /** ID of the related resource */\n resourceId?: string;\n /** Type of the related resource */\n resourceType?: string;\n /** When the operation occurred (ISO 8601) */\n createdAt: string;\n}\n\n/**\n * Response from usage history endpoint\n */\nexport interface UsageHistoryResponse {\n /** List of usage entries */\n entries: UsageHistoryEntry[];\n /** Whether there are more results */\n hasMore: boolean;\n /** Total count (if available) */\n total?: number;\n}\n\n/**\n * Generic API response wrapper\n */\nexport interface ApiResponse<T> {\n success: boolean;\n data?: T;\n error?: {\n code: string;\n message: string;\n details?: unknown;\n };\n}\n\n/**\n * SDK error codes\n */\nexport const SDKErrorCode = {\n NETWORK_ERROR: \"NETWORK_ERROR\",\n AUTHENTICATION_ERROR: \"AUTHENTICATION_ERROR\",\n AUTHORIZATION_ERROR: \"AUTHORIZATION_ERROR\",\n INSUFFICIENT_CREDITS: \"INSUFFICIENT_CREDITS\",\n RESERVATION_NOT_FOUND: \"RESERVATION_NOT_FOUND\",\n RESERVATION_EXPIRED: \"RESERVATION_EXPIRED\",\n VALIDATION_ERROR: \"VALIDATION_ERROR\",\n SERVER_ERROR: \"SERVER_ERROR\",\n UNKNOWN_ERROR: \"UNKNOWN_ERROR\",\n} as const;\n\nexport type SDKErrorCodeType = (typeof SDKErrorCode)[keyof typeof SDKErrorCode];\n","/**\n * SDK error classes\n *\n * Provides typed error handling for SDK operations.\n */\n\nimport { SDKErrorCode, type SDKErrorCodeType } from \"./types.js\";\n\n/**\n * Base error class for SDK errors\n */\nexport class CreditsSDKError extends Error {\n public readonly code: SDKErrorCodeType;\n public readonly details?: Record<string, unknown>;\n public readonly statusCode?: number;\n\n constructor(\n message: string,\n code: SDKErrorCodeType,\n options?: {\n details?: Record<string, unknown>;\n statusCode?: number;\n cause?: Error;\n }\n ) {\n super(message, { cause: options?.cause });\n this.name = \"CreditsSDKError\";\n this.code = code;\n this.details = options?.details;\n this.statusCode = options?.statusCode;\n }\n\n /**\n * Check if this is a specific error type\n */\n is(code: SDKErrorCodeType): boolean {\n return this.code === code;\n }\n\n /**\n * Create a JSON representation for logging\n */\n toJSON(): Record<string, unknown> {\n return {\n name: this.name,\n message: this.message,\n code: this.code,\n details: this.details,\n statusCode: this.statusCode,\n };\n }\n}\n\n/**\n * Error thrown when a network request fails\n */\nexport class NetworkError extends CreditsSDKError {\n constructor(message: string, cause?: Error) {\n super(message, SDKErrorCode.NETWORK_ERROR, { cause });\n this.name = \"NetworkError\";\n }\n}\n\n/**\n * Error thrown when authentication fails\n */\nexport class AuthenticationError extends CreditsSDKError {\n constructor(message: string = \"Authentication required\") {\n super(message, SDKErrorCode.AUTHENTICATION_ERROR, { statusCode: 401 });\n this.name = \"AuthenticationError\";\n }\n}\n\n/**\n * Error thrown when authorization fails\n */\nexport class AuthorizationError extends CreditsSDKError {\n constructor(message: string = \"Access denied\") {\n super(message, SDKErrorCode.AUTHORIZATION_ERROR, { statusCode: 403 });\n this.name = \"AuthorizationError\";\n }\n}\n\n/**\n * Error thrown when credits are insufficient\n */\nexport class InsufficientCreditsError extends CreditsSDKError {\n public readonly available: number;\n public readonly required: number;\n\n constructor(available: number, required: number) {\n super(\n `Insufficient credits: available ${available}, required ${required}`,\n SDKErrorCode.INSUFFICIENT_CREDITS,\n {\n statusCode: 402,\n details: { available, required },\n }\n );\n this.name = \"InsufficientCreditsError\";\n this.available = available;\n this.required = required;\n }\n}\n\n/**\n * Error thrown when a reservation is not found\n */\nexport class ReservationNotFoundError extends CreditsSDKError {\n public readonly reservationId: string;\n\n constructor(reservationId: string) {\n super(\n `Reservation not found: ${reservationId}`,\n SDKErrorCode.RESERVATION_NOT_FOUND,\n {\n statusCode: 404,\n details: { reservationId },\n }\n );\n this.name = \"ReservationNotFoundError\";\n this.reservationId = reservationId;\n }\n}\n\n/**\n * Error thrown when a reservation has expired\n */\nexport class ReservationExpiredError extends CreditsSDKError {\n public readonly reservationId: string;\n\n constructor(reservationId: string) {\n super(\n `Reservation expired: ${reservationId}`,\n SDKErrorCode.RESERVATION_EXPIRED,\n {\n statusCode: 410,\n details: { reservationId },\n }\n );\n this.name = \"ReservationExpiredError\";\n this.reservationId = reservationId;\n }\n}\n\n/**\n * Error thrown for validation failures\n */\nexport class ValidationError extends CreditsSDKError {\n public readonly field?: string;\n\n constructor(message: string, field?: string) {\n super(message, SDKErrorCode.VALIDATION_ERROR, {\n statusCode: 400,\n details: field ? { field } : undefined,\n });\n this.name = \"ValidationError\";\n this.field = field;\n }\n}\n\n/**\n * Error thrown for server errors\n */\nexport class ServerError extends CreditsSDKError {\n constructor(message: string = \"Internal server error\") {\n super(message, SDKErrorCode.SERVER_ERROR, { statusCode: 500 });\n this.name = \"ServerError\";\n }\n}\n\n/**\n * Parse an API error response into the appropriate SDK error\n */\nexport function parseApiError(\n statusCode: number,\n body: { error?: { code?: string; message?: string; details?: unknown } }\n): CreditsSDKError {\n const errorCode = body.error?.code || \"\";\n const errorMessage = body.error?.message || \"An error occurred\";\n const details = body.error?.details as Record<string, unknown> | undefined;\n\n switch (statusCode) {\n case 401:\n return new AuthenticationError(errorMessage);\n case 403:\n return new AuthorizationError(errorMessage);\n case 402:\n // Parse insufficient credits details\n if (details?.available !== undefined && details?.required !== undefined) {\n return new InsufficientCreditsError(\n details.available as number,\n details.required as number\n );\n }\n return new CreditsSDKError(errorMessage, SDKErrorCode.INSUFFICIENT_CREDITS, {\n statusCode,\n details,\n });\n case 404:\n if (errorCode === \"RESERVATION_NOT_FOUND\" && details?.reservationId) {\n return new ReservationNotFoundError(details.reservationId as string);\n }\n return new CreditsSDKError(errorMessage, SDKErrorCode.RESERVATION_NOT_FOUND, {\n statusCode,\n details,\n });\n case 410:\n if (errorCode === \"RESERVATION_EXPIRED\" && details?.reservationId) {\n return new ReservationExpiredError(details.reservationId as string);\n }\n return new CreditsSDKError(errorMessage, SDKErrorCode.RESERVATION_EXPIRED, {\n statusCode,\n details,\n });\n case 400:\n case 422:\n return new ValidationError(errorMessage, details?.field as string | undefined);\n case 500:\n default:\n if (statusCode >= 500) {\n return new ServerError(errorMessage);\n }\n return new CreditsSDKError(errorMessage, SDKErrorCode.UNKNOWN_ERROR, {\n statusCode,\n details,\n });\n }\n}\n","/**\n * CreditsClient - REST API client for the credits system\n *\n * Provides a type-safe interface for consuming credits API endpoints\n * from external services or other applications.\n *\n * @example\n * ```typescript\n * const client = new CreditsClient({\n * baseUrl: process.env.CREDITS_API_URL,\n * getAuthToken: async () => session.accessToken,\n * });\n *\n * // Check if user has sufficient credits\n * const check = await client.checkAvailability(\"story_generation\");\n * if (!check.hasCredits) {\n * console.log(\"Insufficient credits\");\n * return;\n * }\n *\n * // Two-phase commit pattern\n * const reservation = await client.reserve(\"story_generation\");\n * try {\n * const result = await doExpensiveWork();\n * await client.commit(reservation.reservationId);\n * return result;\n * } catch (error) {\n * await client.release(reservation.reservationId);\n * throw error;\n * }\n * ```\n */\n\nimport type {\n CreditsClientConfig,\n UserCredits,\n CreditCheckResult,\n ReservationResult,\n UsageHistoryResponse,\n PaginationOptions,\n} from \"./types.js\";\nimport { parseApiError, NetworkError } from \"./errors.js\";\n\nexport class CreditsClient {\n private readonly baseUrl: string;\n private readonly getAuthToken?: () => Promise<string>;\n private readonly fetchFn: typeof fetch;\n\n constructor(config: CreditsClientConfig) {\n // Ensure baseUrl doesn't have trailing slash\n this.baseUrl = config.baseUrl.replace(/\\/$/, \"\");\n this.getAuthToken = config.getAuthToken;\n this.fetchFn = config.fetch ?? fetch;\n }\n\n /**\n * Make an authenticated request to the API\n */\n private async request<T>(\n path: string,\n options: {\n method?: \"GET\" | \"POST\" | \"PUT\" | \"DELETE\";\n body?: unknown;\n params?: Record<string, string | number | undefined>;\n } = {}\n ): Promise<T> {\n const { method = \"GET\", body, params } = options;\n\n // Build URL with query params\n let url = `${this.baseUrl}${path}`;\n if (params) {\n const searchParams = new URLSearchParams();\n for (const [key, value] of Object.entries(params)) {\n if (value !== undefined) {\n searchParams.set(key, String(value));\n }\n }\n const queryString = searchParams.toString();\n if (queryString) {\n url += `?${queryString}`;\n }\n }\n\n // Build headers\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n };\n\n // Add auth token if available\n if (this.getAuthToken) {\n try {\n const token = await this.getAuthToken();\n if (token) {\n headers[\"Authorization\"] = `Bearer ${token}`;\n }\n } catch {\n // Auth token not available, proceed without it\n }\n }\n\n // Make the request\n let response: Response;\n try {\n response = await this.fetchFn(url, {\n method,\n headers,\n body: body ? JSON.stringify(body) : undefined,\n });\n } catch (error) {\n throw new NetworkError(\n error instanceof Error ? error.message : \"Network request failed\",\n error instanceof Error ? error : undefined\n );\n }\n\n // Parse response\n const responseBody = (await response.json()) as {\n data?: T;\n error?: { code?: string; message?: string; details?: unknown };\n };\n\n // Handle errors\n if (!response.ok) {\n throw parseApiError(response.status, responseBody);\n }\n\n // Return data\n return responseBody.data as T;\n }\n\n /**\n * Get the current user's credit balance\n *\n * @returns User's credit information\n * @throws AuthenticationError if not authenticated\n */\n async getBalance(): Promise<UserCredits> {\n return this.request<UserCredits>(\"/api/v1/credits/balance\");\n }\n\n /**\n * Check if the user has sufficient credits for an operation\n *\n * @param operationType - Type of operation to check\n * @param customCost - Optional custom cost to check against\n * @returns Credit availability status\n * @throws AuthenticationError if not authenticated\n */\n async checkAvailability(\n operationType: string,\n customCost?: number\n ): Promise<CreditCheckResult> {\n return this.request<CreditCheckResult>(\"/api/v1/credits/availability\", {\n params: {\n operationType,\n customCost: customCost?.toString(),\n },\n });\n }\n\n /**\n * Reserve credits for an operation\n *\n * This is the first step in the two-phase commit pattern.\n * Reserved credits are held for 5 minutes before expiring.\n *\n * @param operationType - Type of operation\n * @param options - Optional reservation options\n * @returns Reservation details including ID and expiry\n * @throws InsufficientCreditsError if not enough credits\n * @throws AuthenticationError if not authenticated\n */\n async reserve(\n operationType: string,\n options?: {\n customCost?: number;\n resourceId?: string;\n resourceType?: string;\n }\n ): Promise<ReservationResult> {\n return this.request<ReservationResult>(\"/api/v1/credits/reserve\", {\n method: \"POST\",\n body: {\n operationType,\n ...options,\n },\n });\n }\n\n /**\n * Commit a reservation (deduct credits)\n *\n * This is the second step in the two-phase commit pattern.\n * Call this after your operation succeeds.\n *\n * @param reservationId - ID of the reservation to commit\n * @throws ReservationNotFoundError if reservation doesn't exist\n * @throws ReservationExpiredError if reservation has expired\n * @throws AuthenticationError if not authenticated\n */\n async commit(reservationId: string): Promise<void> {\n await this.request<{ message: string }>(\"/api/v1/credits/commit\", {\n method: \"POST\",\n body: { reservationId },\n });\n }\n\n /**\n * Release a reservation (refund credits)\n *\n * Call this if your operation fails after reserving credits.\n *\n * @param reservationId - ID of the reservation to release\n * @throws ReservationNotFoundError if reservation doesn't exist\n * @throws AuthenticationError if not authenticated\n */\n async release(reservationId: string): Promise<void> {\n await this.request<{ message: string }>(\"/api/v1/credits/release\", {\n method: \"POST\",\n body: { reservationId },\n });\n }\n\n /**\n * Get usage history for the current user\n *\n * @param options - Optional pagination options\n * @returns Paginated list of usage entries\n * @throws AuthenticationError if not authenticated\n */\n async getHistory(options?: PaginationOptions): Promise<UsageHistoryResponse> {\n return this.request<UsageHistoryResponse>(\"/api/v1/credits/history\", {\n params: {\n page: options?.page,\n limit: options?.limit,\n },\n });\n }\n\n /**\n * Get the available credits (balance + bonus - reserved)\n *\n * Convenience method that calculates available credits from balance.\n *\n * @returns Number of available credits\n * @throws AuthenticationError if not authenticated\n */\n async getAvailableCredits(): Promise<number> {\n const balance = await this.getBalance();\n return balance.balance + balance.bonusCredits - balance.reserved;\n }\n\n /**\n * Execute an operation with automatic credit handling\n *\n * This is a convenience method that handles the full two-phase commit\n * pattern automatically.\n *\n * @param operationType - Type of operation\n * @param operation - The operation to execute\n * @param options - Optional reservation options\n * @returns Result of the operation\n * @throws InsufficientCreditsError if not enough credits\n * @throws AuthenticationError if not authenticated\n *\n * @example\n * ```typescript\n * const result = await client.withCredits(\n * \"story_generation\",\n * async () => {\n * return generateStory(data);\n * }\n * );\n * ```\n */\n async withCredits<T>(\n operationType: string,\n operation: () => Promise<T>,\n options?: {\n customCost?: number;\n resourceId?: string;\n resourceType?: string;\n }\n ): Promise<T> {\n const reservation = await this.reserve(operationType, options);\n\n try {\n const result = await operation();\n await this.commit(reservation.reservationId);\n return result;\n } catch (error) {\n // Always attempt to release on failure\n try {\n await this.release(reservation.reservationId);\n } catch {\n // Ignore release errors, original error is more important\n }\n throw error;\n }\n }\n}\n","/**\n * AdminCreditsClient - Admin REST API client for the credits system\n *\n * Provides administrative access to the credits system for managing\n * user credits, configurations, and subscriptions.\n *\n * @example\n * ```typescript\n * const adminClient = new AdminCreditsClient({\n * baseUrl: process.env.CREDITS_API_URL,\n * apiKey: process.env.CREDITS_ADMIN_API_KEY,\n * });\n *\n * // Add credits to a user\n * await adminClient.addCredits(\"user-123\", 100, \"Support credit grant\");\n *\n * // Update subscription tier\n * await adminClient.updateSubscription(\"user-123\", \"premium\", \"2027-01-01T00:00:00Z\");\n *\n * // Update operation costs\n * await adminClient.updateCosts({\n * story_generation: 8,\n * image_generation: 12,\n * });\n * ```\n */\n\nimport type { AdminCreditsClientConfig, UserCredits } from \"./types.js\";\nimport { parseApiError, NetworkError } from \"./errors.js\";\n\n/**\n * Credits configuration returned by the API\n */\nexport interface CreditsConfig {\n operationCosts: Record<string, number>;\n tierConfigs: Record<\n string,\n {\n monthlyLimit: number;\n features: string[];\n }\n >;\n}\n\n/**\n * Response from listing users\n */\nexport interface ListUsersResponse {\n users: Array<{\n userId: string;\n balance: number;\n bonusCredits?: number;\n tier: string;\n monthlyUsed?: number;\n }>;\n hasMore: boolean;\n total?: number;\n}\n\n/**\n * Options for listing users\n */\nexport interface ListUsersOptions {\n page?: number;\n limit?: number;\n tier?: string;\n search?: string;\n}\n\nexport class AdminCreditsClient {\n private readonly baseUrl: string;\n private readonly apiKey: string;\n private readonly fetchFn: typeof fetch;\n\n constructor(config: AdminCreditsClientConfig) {\n // Ensure baseUrl doesn't have trailing slash\n this.baseUrl = config.baseUrl.replace(/\\/$/, \"\");\n this.apiKey = config.apiKey;\n this.fetchFn = config.fetch ?? fetch;\n }\n\n /**\n * Make an authenticated admin request to the API\n */\n private async request<T>(\n path: string,\n options: {\n method?: \"GET\" | \"POST\" | \"PUT\" | \"DELETE\";\n body?: unknown;\n params?: Record<string, string | number | undefined>;\n } = {}\n ): Promise<T> {\n const { method = \"GET\", body, params } = options;\n\n // Build URL with query params\n let url = `${this.baseUrl}${path}`;\n if (params) {\n const searchParams = new URLSearchParams();\n for (const [key, value] of Object.entries(params)) {\n if (value !== undefined) {\n searchParams.set(key, String(value));\n }\n }\n const queryString = searchParams.toString();\n if (queryString) {\n url += `?${queryString}`;\n }\n }\n\n // Build headers with admin API key\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n \"x-api-key\": this.apiKey,\n };\n\n // Make the request\n let response: Response;\n try {\n response = await this.fetchFn(url, {\n method,\n headers,\n body: body ? JSON.stringify(body) : undefined,\n });\n } catch (error) {\n throw new NetworkError(\n error instanceof Error ? error.message : \"Network request failed\",\n error instanceof Error ? error : undefined\n );\n }\n\n // Parse response\n const responseBody = (await response.json()) as {\n data?: T;\n error?: { code?: string; message?: string; details?: unknown };\n };\n\n // Handle errors\n if (!response.ok) {\n throw parseApiError(response.status, responseBody);\n }\n\n // Return data\n return responseBody.data as T;\n }\n\n /**\n * Get a specific user's credits\n *\n * @param userId - ID of the user\n * @returns User's credit information\n * @throws AuthorizationError if API key is invalid\n */\n async getUserCredits(userId: string): Promise<UserCredits> {\n return this.request<UserCredits>(`/api/admin/credits/users/${userId}`);\n }\n\n /**\n * Add credits to a user's account\n *\n * Credits are added to bonusCredits, which don't expire on monthly reset.\n *\n * @param userId - ID of the user\n * @param amount - Amount of credits to add\n * @param description - Reason for adding credits\n * @throws AuthorizationError if API key is invalid\n */\n async addCredits(\n userId: string,\n amount: number,\n description: string\n ): Promise<void> {\n await this.request<{ message: string; newBalance: number }>(\n `/api/admin/credits/users/${userId}/add`,\n {\n method: \"POST\",\n body: { amount, description },\n }\n );\n }\n\n /**\n * Update a user's subscription tier\n *\n * @param userId - ID of the user\n * @param tier - New subscription tier\n * @param expiresAt - Optional expiration date (ISO 8601)\n * @throws AuthorizationError if API key is invalid\n */\n async updateSubscription(\n userId: string,\n tier: string,\n expiresAt?: string\n ): Promise<void> {\n const body: { tier: string; expiresAt?: string } = { tier };\n if (expiresAt) {\n body.expiresAt = expiresAt;\n }\n\n await this.request<{ message: string }>(\n `/api/admin/credits/users/${userId}/subscription`,\n {\n method: \"PUT\",\n body,\n }\n );\n }\n\n /**\n * Get the current credits configuration\n *\n * @returns Credits system configuration\n * @throws AuthorizationError if API key is invalid\n */\n async getConfig(): Promise<CreditsConfig> {\n return this.request<CreditsConfig>(\"/api/admin/credits/config\");\n }\n\n /**\n * Update operation costs\n *\n * @param costs - New operation costs (operation type -> cost)\n * @throws AuthorizationError if API key is invalid\n */\n async updateCosts(costs: Record<string, number>): Promise<void> {\n await this.request<{ message: string }>(\"/api/admin/credits/config/costs\", {\n method: \"PUT\",\n body: { operationCosts: costs },\n });\n }\n\n /**\n * List all users with their credits\n *\n * @param options - Pagination and filtering options\n * @returns Paginated list of users\n * @throws AuthorizationError if API key is invalid\n */\n async listUsers(options?: ListUsersOptions): Promise<ListUsersResponse> {\n return this.request<ListUsersResponse>(\"/api/admin/credits/users\", {\n params: {\n page: options?.page,\n limit: options?.limit,\n tier: options?.tier,\n search: options?.search,\n },\n });\n }\n\n /**\n * Deduct credits from a user's account\n *\n * @param userId - ID of the user\n * @param amount - Amount of credits to deduct\n * @param description - Reason for deducting credits\n * @throws AuthorizationError if API key is invalid\n */\n async deductCredits(\n userId: string,\n amount: number,\n description: string\n ): Promise<void> {\n await this.request<{ message: string; newBalance: number }>(\n `/api/admin/credits/users/${userId}/deduct`,\n {\n method: \"POST\",\n body: { amount, description },\n }\n );\n }\n\n /**\n * Get usage statistics for a user\n *\n * @param userId - ID of the user\n * @param options - Optional date range\n * @returns Usage statistics\n * @throws AuthorizationError if API key is invalid\n */\n async getUserStats(\n userId: string,\n options?: { startDate?: string; endDate?: string }\n ): Promise<{\n totalCreditsUsed: number;\n operationBreakdown: Record<string, number>;\n successRate: number;\n }> {\n return this.request<{\n totalCreditsUsed: number;\n operationBreakdown: Record<string, number>;\n successRate: number;\n }>(`/api/admin/credits/users/${userId}/stats`, {\n params: {\n startDate: options?.startDate,\n endDate: options?.endDate,\n },\n });\n }\n}\n","/**\n * Credit error utilities\n *\n * Provides functions to parse and identify credit-related errors\n * for consistent error handling across the application.\n */\n\n/**\n * Parsed information from a credit error message\n */\nexport interface CreditErrorInfo {\n /** Credits currently available */\n available: number;\n /** Credits required for the operation */\n required: number;\n /** Difference between required and available */\n shortfall: number;\n /** Original error message */\n rawMessage: string;\n}\n\n/**\n * Parse a credit error message to extract available/required credits\n *\n * @param error - Error message string\n * @returns Parsed credit info or null if not a valid credit error format\n *\n * @example\n * ```ts\n * const error = \"Insufficient credits. Available: 3, Required: 10\";\n * const info = parseCreditError(error);\n * // { available: 3, required: 10, shortfall: 7, rawMessage: \"...\" }\n * ```\n */\nexport function parseCreditError(error: string): CreditErrorInfo | null {\n // Pattern: Available: X, Required: Y (case insensitive, flexible whitespace)\n const match = error.match(/Available:\\s*(\\d+)\\s*,\\s*Required:\\s*(\\d+)/i);\n\n if (!match) {\n return null;\n }\n\n const available = parseInt(match[1], 10);\n const required = parseInt(match[2], 10);\n\n // Validate parsed numbers\n if (isNaN(available) || isNaN(required)) {\n return null;\n }\n\n return {\n available,\n required,\n shortfall: required - available,\n rawMessage: error,\n };\n}\n\n/**\n * Check if an error message is a credit-related error\n *\n * @param error - Error message string\n * @returns true if the error is credit-related\n *\n * @example\n * ```ts\n * if (isCreditError(error)) {\n * showCreditErrorDialog(parseCreditError(error));\n * }\n * ```\n */\nexport function isCreditErrorMessage(error: string): boolean {\n const lowerError = error.toLowerCase();\n return (\n lowerError.includes(\"insufficient credits\") ||\n lowerError.includes(\"not enough credits\")\n );\n}\n","/**\n * Credit utility functions\n *\n * Common helper functions for credit calculations\n */\n\nimport type { PortableReservation } from \"../core/index.js\";\n\nexport {\n parseCreditError,\n isCreditErrorMessage,\n type CreditErrorInfo,\n} from \"./error-utils.js\";\n\n/**\n * Calculate total available credits from balance and bonus credits\n *\n * @param credits - Object containing balance and bonusCredits properties\n * @returns Total credits (balance + bonusCredits)\n *\n * @example\n * ```ts\n * const total = getTotalCredits({ balance: 100, bonusCredits: 50 });\n * // Returns 150\n * ```\n */\nexport function getTotalCredits(credits: {\n balance: number;\n bonusCredits: number;\n}): number {\n return credits.balance + credits.bonusCredits;\n}\n\n/**\n * Generate a unique request ID\n *\n * Format: req_{timestamp}_{randomPart}\n * Example: req_1706234567890_a1b2c3d4e5f6\n *\n * @returns URL-safe request ID string\n */\nexport function generateRequestId(): string {\n const timestamp = Date.now();\n const randomPart = Math.random().toString(36).substring(2, 14);\n return `req_${timestamp}_${randomPart}`;\n}\n\n/**\n * Check if the input data indicates preview mode\n *\n * Preview mode skips actual credit deduction - used for dry runs\n * and UI previews where the user wants to see results without committing.\n *\n * @param data - The input data to check\n * @returns true if data has preview: true\n */\nexport function isPreviewMode(data: unknown): boolean {\n return (\n typeof data === \"object\" &&\n data !== null &&\n \"preview\" in data &&\n (data as Record<string, unknown>).preview === true\n );\n}\n\n/**\n * Create a dummy reservation for preview mode\n *\n * Used when `isPreviewMode(data)` returns true to provide\n * a valid reservation object without actual credit operations.\n *\n * @param userId - The user ID\n * @param operationType - The operation type\n * @returns A dummy PortableReservation with id \"preview-mode\"\n */\nexport function createDummyReservation(\n userId: string,\n operationType: string\n): PortableReservation {\n const now = new Date().toISOString();\n return {\n id: \"preview-mode\",\n userId,\n amount: 0,\n operationType,\n status: \"released\",\n createdAt: now,\n expiresAt: now,\n };\n}\n"],"mappings":"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","names":["createDummyReservation","generateRequestId","isPreviewMode","toDate","toDate","getOperationCost","generateRequestId","isPreviewMode","createDummyReservation"]}