{"version":3,"sources":["../../src/service/index.ts","../../src/core/types.ts","../../src/repository/types.ts","../../src/config/index.ts","../../src/config/provider.ts","../../src/service/credits-service.ts"],"sourcesContent":["export {\r\n  CreditsService,\r\n  createCreditsService,\r\n} from \"./credits-service.js\";\r\n\r\nexport type {\r\n  LowBalanceNotificationCallback,\r\n  SubscriptionExpiredNotificationCallback,\r\n} from \"./credits-service.js\";\r\n","/**\r\n * Portable credits types - framework and database agnostic\r\n *\r\n * These types use standard JavaScript types (string, Date) instead of\r\n * Firebase-specific types (Timestamp). This makes the credits system\r\n * portable to other environments without Firebase dependencies.\r\n *\r\n * Note: Firestore Timestamp is handled by the credits-firestore adapter.\r\n */\r\n\r\n// ==================== Utility Functions ====================\r\n\r\n/**\r\n * Calculate available credits from balance, bonus, and reserved amounts.\r\n *\r\n * Available = balance + bonusCredits - reserved\r\n *\r\n * @param balance - Regular monthly credits\r\n * @param bonusCredits - Purchased/admin credits\r\n * @param reserved - Credits locked for in-flight operations\r\n * @returns Available credits for new operations\r\n */\r\nexport function calculateAvailableCredits(\r\n  balance: number,\r\n  bonusCredits: number,\r\n  reserved: number\r\n): number {\r\n  return balance + bonusCredits - reserved;\r\n}\r\n\r\n/**\r\n * Convert various timestamp formats to ISO string.\r\n *\r\n * Handles:\r\n * - Date objects\r\n * - ISO string (returned as-is)\r\n * - Timestamp-like objects with toDate() method (Firebase)\r\n * - undefined/null (returns current time)\r\n *\r\n * @param value - Value to convert\r\n * @returns ISO 8601 string\r\n */\r\nexport function toPortableTimestamp(value: unknown): string {\r\n  if (!value) {\r\n    return new Date().toISOString();\r\n  }\r\n\r\n  if (typeof value === \"string\") {\r\n    return value;\r\n  }\r\n\r\n  if (value instanceof Date) {\r\n    return value.toISOString();\r\n  }\r\n\r\n  if (\r\n    typeof value === \"object\" &&\r\n    value !== null &&\r\n    \"toDate\" in value &&\r\n    typeof (value as { toDate: () => Date }).toDate === \"function\"\r\n  ) {\r\n    return (value as { toDate: () => Date }).toDate().toISOString();\r\n  }\r\n\r\n  return new Date().toISOString();\r\n}\r\n\r\n/**\r\n * Convert various timestamp formats to Date object.\r\n *\r\n * Handles:\r\n * - Date objects (returned as-is)\r\n * - ISO string\r\n * - Timestamp-like objects with toDate() method (Firebase)\r\n * - Invalid values (returns current date)\r\n *\r\n * @param value - Value to convert\r\n * @returns Date object\r\n */\r\nexport function toDate(value: unknown): Date {\r\n  if (value instanceof Date) {\r\n    return value;\r\n  }\r\n\r\n  if (typeof value === \"string\") {\r\n    return new Date(value);\r\n  }\r\n\r\n  if (\r\n    typeof value === \"object\" &&\r\n    value !== null &&\r\n    \"toDate\" in value &&\r\n    typeof (value as { toDate: () => Date }).toDate === \"function\"\r\n  ) {\r\n    return (value as { toDate: () => Date }).toDate();\r\n  }\r\n\r\n  return new Date();\r\n}\r\n\r\n// ==================== Subscription Types ====================\r\n\r\n/**\r\n * Built-in tier ids (autocomplete) — apps may add more via config.\r\n */\r\nexport type BuiltinTier = \"free\" | \"basic\" | \"premium\" | \"unlimited\";\r\n\r\n/**\r\n * Subscription tiers for the credits system.\r\n *\r\n * Any built-in OR any config-defined tier id. The `(string & {})` keeps builtin\r\n * autocomplete while accepting arbitrary configured tiers.\r\n */\r\nexport type SubscriptionTier = BuiltinTier | (string & {});\r\n\r\n// ==================== Operation Types ====================\r\n\r\n/**\r\n * Operation types for cost tracking (provider-agnostic)\r\n *\r\n * Dynamic string type - any operation configured in the system is valid.\r\n */\r\nexport type CreditOperationType = string;\r\n\r\n/**\r\n * AI providers\r\n */\r\nexport type AIProviderType = \"gemini\";\r\n\r\n/**\r\n * Resource type for usage logging\r\n */\r\nexport type ResourceType = string;\r\n\r\n// ==================== User Credits ====================\r\n\r\n/**\r\n * Portable user credits balance\r\n *\r\n * Uses ISO string timestamps instead of Firebase Timestamp.\r\n * This type is safe for JSON serialization and works across frameworks.\r\n */\r\nexport interface PortableUserCredits {\r\n  userId: string;\r\n  /** Current available balance (monthly credits, reset each month) */\r\n  balance: number;\r\n  /** Bonus credits from purchases/admin (never reset, persist until used) */\r\n  bonusCredits: number;\r\n  /** Credits currently reserved for in-flight operations */\r\n  reserved: number;\r\n  /** User's subscription tier */\r\n  tier: SubscriptionTier;\r\n  /** Monthly credit limit based on tier (0 = unlimited) */\r\n  monthlyLimit: number;\r\n  /** Credits used this month (resets monthly) */\r\n  monthlyUsed: number;\r\n  /** When monthly credits reset (start of next month) - ISO 8601 */\r\n  monthlyResetAt: string;\r\n  /** Subscription expiry (null/undefined for free tier) - ISO 8601 or null */\r\n  subscriptionExpiresAt?: string | null;\r\n  /** Creation timestamp - ISO 8601 */\r\n  createdAt: string;\r\n  /** Last update timestamp - ISO 8601 */\r\n  updatedAt: string;\r\n}\r\n\r\n// ==================== Reservation Types ====================\r\n\r\n/**\r\n * Credit reservation status for two-phase commit\r\n */\r\nexport type ReservationStatus = \"reserved\" | \"committed\" | \"released\" | \"expired\";\r\n\r\n/**\r\n * Portable credit reservation\r\n *\r\n * Used for two-phase commit to prevent double-spending.\r\n */\r\nexport interface PortableReservation {\r\n  id: string;\r\n  userId: string;\r\n  /** Amount of credits reserved */\r\n  amount: number;\r\n  /** Operation type for tracking */\r\n  operationType: CreditOperationType;\r\n  /** Current status of reservation */\r\n  status: ReservationStatus;\r\n  /** When reservation was created - ISO 8601 */\r\n  createdAt: string;\r\n  /** When reservation expires (for cleanup) - ISO 8601 */\r\n  expiresAt: string;\r\n  /** When reservation was committed/released - ISO 8601 or undefined */\r\n  completedAt?: string;\r\n}\r\n\r\n// ==================== Result Types ====================\r\n\r\n/**\r\n * Result of a credit check operation\r\n */\r\nexport interface CreditCheckResult {\r\n  /** Whether user has sufficient credits */\r\n  hasCredits: boolean;\r\n  /** Current balance (balance + bonusCredits) */\r\n  balance: number;\r\n  /** Required credits for operation */\r\n  required: number;\r\n  /** Shortfall if insufficient */\r\n  shortfall: number;\r\n}\r\n\r\n/**\r\n * Result of monthly reset operation\r\n */\r\nexport interface MonthlyResetResult {\r\n  /** Whether the reset was performed */\r\n  wasReset: boolean;\r\n  /** The user credits after the operation */\r\n  credits: PortableUserCredits;\r\n}\r\n\r\n/**\r\n * Result of subscription expiry check\r\n */\r\nexport interface SubscriptionExpiryResult {\r\n  /** Whether the subscription was downgraded */\r\n  wasDowngraded: boolean;\r\n  /** Whether user is in grace period */\r\n  inGracePeriod: boolean;\r\n  /** Days remaining in grace period (0 if not in grace) */\r\n  graceDaysRemaining: number;\r\n  /** The user credits after the operation */\r\n  credits: PortableUserCredits;\r\n}\r\n\r\n// ==================== Transaction Types ====================\r\n\r\n/**\r\n * Credit transaction type\r\n */\r\nexport type TransactionType = \"purchase\" | \"subscription\" | \"bonus\" | \"refund\" | \"adjustment\";\r\n\r\n/**\r\n * Portable credit transaction\r\n */\r\nexport interface PortableTransaction {\r\n  id: string;\r\n  userId: string;\r\n  type: TransactionType;\r\n  /** Amount of credits (positive for additions, negative for deductions) */\r\n  amount: number;\r\n  description: string;\r\n  /** Reference to payment provider */\r\n  paymentRef?: string;\r\n  previousBalance: number;\r\n  newBalance: number;\r\n  createdAt: string;\r\n}\r\n\r\n// ==================== Journal Types ====================\r\n\r\n/**\r\n * Source of a credit journal entry\r\n */\r\nexport type CreditSource =\r\n  | \"operation_commit\"\r\n  | \"operation_release\"\r\n  | \"purchase\"\r\n  | \"subscription_grant\"\r\n  | \"subscription_upgrade\"\r\n  | \"subscription_downgrade\"\r\n  | \"monthly_reset\"\r\n  | \"bonus\"\r\n  | \"refund\"\r\n  | \"admin_adjustment\"\r\n  | \"expiry\"\r\n  | \"reservation_expired\";\r\n\r\n/**\r\n * Type of reference for a journal entry\r\n */\r\nexport type JournalReferenceType =\r\n  | \"reservation\"\r\n  | \"transaction\"\r\n  | \"adjustment\"\r\n  | \"reset\"\r\n  | \"subscription\";\r\n\r\n/**\r\n * Portable credit journal entry for audit trail\r\n */\r\nexport interface PortableJournalEntry {\r\n  id: string;\r\n  userId: string;\r\n  /** Type of entry - debit decreases balance, credit increases balance */\r\n  entryType: \"debit\" | \"credit\";\r\n  /** Amount of credits involved */\r\n  amount: number;\r\n  /** Balance after this entry */\r\n  balanceAfter: number;\r\n  /** Source of the credit change */\r\n  source: CreditSource;\r\n  /** Reference ID (e.g., reservationId, transactionId) */\r\n  referenceId: string;\r\n  /** Type of reference */\r\n  referenceType: JournalReferenceType;\r\n  /** Human-readable description */\r\n  description: string;\r\n  /** Additional metadata */\r\n  metadata?: Record<string, unknown>;\r\n  /** When this entry was created - ISO 8601 */\r\n  createdAt: string;\r\n}\r\n\r\n// ==================== Usage Types ====================\r\n\r\n/**\r\n * Portable usage log entry\r\n */\r\nexport interface PortableUsageLog {\r\n  id: string;\r\n  userId: string;\r\n  operationType: CreditOperationType;\r\n  provider: AIProviderType;\r\n  creditsUsed: number;\r\n  success: boolean;\r\n  errorMessage?: string;\r\n  resourceId?: string;\r\n  resourceType?: ResourceType;\r\n  requestId?: string;\r\n  metadata?: Record<string, unknown>;\r\n  createdAt: string;\r\n}\r\n\r\n/**\r\n * User-friendly usage history entry\r\n */\r\nexport interface UsageHistoryEntry {\r\n  id: string;\r\n  /** Type of entry for display */\r\n  type: \"usage\" | \"purchase\" | \"bonus\" | \"reset\" | \"refund\" | \"adjustment\";\r\n  /** Positive = earned, Negative = spent */\r\n  creditsChange: number;\r\n  /** Balance after this entry */\r\n  balanceAfter: number;\r\n  /** Human-readable description */\r\n  description: string;\r\n  /** When this occurred - ISO 8601 */\r\n  createdAt: string;\r\n}\r\n\r\n/**\r\n * Paginated usage history response\r\n */\r\nexport interface UsageHistoryResponse {\r\n  entries: UsageHistoryEntry[];\r\n  pagination: {\r\n    total: number;\r\n    limit: number;\r\n    offset: number;\r\n    hasMore: boolean;\r\n  };\r\n}\r\n\r\n// ==================== Tier Configuration ====================\r\n\r\n/**\r\n * Tier configuration for credit limits and pricing\r\n */\r\nexport interface TierConfig {\r\n  tier: SubscriptionTier;\r\n  monthlyCredits: number;\r\n  priceUsd: number;\r\n  features: string[];\r\n  /** Free/default tier marker — no subscription expiry, balance untouched on tier change. Defaults to priceUsd === 0. */\r\n  isFree?: boolean;\r\n  /** Unlimited tier marker. Defaults to monthlyCredits === 0. */\r\n  unlimited?: boolean;\r\n  /** Tier assigned to brand-new users. Defaults to the tier flagged isFree. */\r\n  isDefault?: boolean;\r\n}\r\n\r\n// ==================== Options Types ====================\r\n\r\n/**\r\n * Options for withCredits HOF\r\n */\r\nexport interface WithCreditsOptions {\r\n  /** Operation type for cost lookup */\r\n  operationType: CreditOperationType;\r\n  /** AI provider (defaults to \"gemini\") */\r\n  provider?: AIProviderType;\r\n  /** Custom cost override (use with caution) */\r\n  customCost?: number;\r\n  /** Resource ID for usage logging */\r\n  resourceId?: string;\r\n  /** Resource type for usage logging */\r\n  resourceType?: ResourceType;\r\n}\r\n\r\n// ==================== Constants ====================\r\n","import type {\r\n  PortableUserCredits,\r\n  PortableReservation,\r\n  PortableTransaction,\r\n  PortableUsageLog,\r\n  PortableJournalEntry,\r\n  CreditOperationType,\r\n  SubscriptionTier,\r\n  ReservationStatus,\r\n  AIProviderType,\r\n  MonthlyResetResult,\r\n  SubscriptionExpiryResult,\r\n  CreditSource,\r\n  JournalReferenceType,\r\n} from \"../core/types.js\";\r\n\r\n/**\r\n * Input for creating a credit reservation\r\n */\r\nexport interface CreateReservationInput {\r\n  userId: string;\r\n  amount: number;\r\n  operationType: CreditOperationType;\r\n  expiresAt: Date;\r\n}\r\n\r\n/**\r\n * Input for creating a credit transaction\r\n */\r\nexport interface CreateTransactionInput {\r\n  userId: string;\r\n  type: PortableTransaction[\"type\"];\r\n  amount: number;\r\n  description: string;\r\n  paymentRef?: string;\r\n  previousBalance: number;\r\n  newBalance: number;\r\n}\r\n\r\n/**\r\n * Input for logging usage\r\n */\r\nexport interface CreateUsageLogInput {\r\n  userId: string;\r\n  operationType: CreditOperationType;\r\n  provider: AIProviderType;\r\n  creditsUsed: number;\r\n  success: boolean;\r\n  errorMessage?: string;\r\n  resourceId?: string;\r\n  resourceType?: string;\r\n  requestId?: string;\r\n  metadata?: Record<string, unknown>;\r\n}\r\n\r\n/**\r\n * Query options for usage logs\r\n */\r\nexport interface UsageLogQuery {\r\n  userId?: string;\r\n  operationType?: CreditOperationType;\r\n  success?: boolean;\r\n  limit?: number;\r\n  offset?: number;\r\n  startDate?: Date;\r\n  endDate?: Date;\r\n}\r\n\r\n/**\r\n * Input for creating a journal entry\r\n */\r\nexport interface CreateJournalEntryInput {\r\n  userId: string;\r\n  entryType: \"debit\" | \"credit\";\r\n  amount: number;\r\n  balanceAfter: number;\r\n  source: CreditSource;\r\n  referenceId: string;\r\n  referenceType: JournalReferenceType;\r\n  description: string;\r\n  metadata?: Record<string, unknown>;\r\n}\r\n\r\n/**\r\n * Query options for journal entries\r\n */\r\nexport interface JournalEntryQuery {\r\n  userId: string;\r\n  source?: CreditSource;\r\n  referenceType?: JournalReferenceType;\r\n  limit?: number;\r\n  offset?: number;\r\n  startDate?: Date;\r\n  endDate?: Date;\r\n}\r\n\r\n/**\r\n * Partial update for user credits balance\r\n */\r\nexport interface CreditBalanceUpdate {\r\n  balance?: number;\r\n  bonusCredits?: number;\r\n  reserved?: number;\r\n  tier?: SubscriptionTier;\r\n  monthlyLimit?: number;\r\n  monthlyUsed?: number;\r\n  monthlyResetAt?: Date | string;\r\n  subscriptionExpiresAt?: Date | string | null;\r\n  /** Increment balance by this amount (alternative to absolute value) */\r\n  balanceIncrement?: number;\r\n  /** Increment bonusCredits by this amount (alternative to absolute value) */\r\n  bonusCreditsIncrement?: number;\r\n  /** Increment reserved by this amount (alternative to absolute value) */\r\n  reservedIncrement?: number;\r\n  /** Increment monthlyUsed by this amount */\r\n  monthlyUsedIncrement?: number;\r\n}\r\n\r\n/**\r\n * Input for tier update\r\n */\r\nexport interface TierUpdateInput {\r\n  tier: SubscriptionTier;\r\n  monthlyLimit: number;\r\n  balance?: number;\r\n  monthlyUsed?: number;\r\n  subscriptionExpiresAt?: Date | string | null;\r\n}\r\n\r\n/**\r\n * Repository interface for credits database operations\r\n *\r\n * Implementations can use any database (Firestore, PostgreSQL, etc.)\r\n * All methods should handle their own error handling and transactions\r\n */\r\nexport interface ICreditRepository {\r\n  // ==================== User Credits ====================\r\n\r\n  /**\r\n   * Get user credits balance\r\n   * @param userId - User ID\r\n   * @returns User credits or null if not found\r\n   */\r\n  getUserCredits(userId: string): Promise<PortableUserCredits | null>;\r\n\r\n  /**\r\n   * Initialize credits for a new user\r\n   * @param userId - User ID\r\n   * @param tier - Initial subscription tier\r\n   * @param initialBalance - Initial credit balance\r\n   * @returns Initialized user credits\r\n   */\r\n  initializeUserCredits(\r\n    userId: string,\r\n    tier: SubscriptionTier,\r\n    initialBalance: number\r\n  ): Promise<PortableUserCredits>;\r\n\r\n  /**\r\n   * Update user credits balance\r\n   * @param userId - User ID\r\n   * @param updates - Partial updates to apply\r\n   */\r\n  updateUserCredits(userId: string, updates: CreditBalanceUpdate): Promise<void>;\r\n\r\n  /**\r\n   * Update user subscription tier\r\n   * @param userId - User ID\r\n   * @param input - Tier update data\r\n   */\r\n  updateUserTier(userId: string, input: TierUpdateInput): Promise<void>;\r\n\r\n  // ==================== Reservations ====================\r\n\r\n  /**\r\n   * Create a credit reservation (phase 1 of two-phase commit)\r\n   * @param input - Reservation data\r\n   * @returns Created reservation\r\n   */\r\n  createReservation(input: CreateReservationInput): Promise<PortableReservation>;\r\n\r\n  /**\r\n   * Get a reservation by ID\r\n   * @param userId - User ID\r\n   * @param reservationId - Reservation ID\r\n   * @returns Reservation or null\r\n   */\r\n  getReservation(userId: string, reservationId: string): Promise<PortableReservation | null>;\r\n\r\n  /**\r\n   * Update reservation status\r\n   * @param userId - User ID\r\n   * @param reservationId - Reservation ID\r\n   * @param status - New status\r\n   * @param completedAt - Completion timestamp\r\n   */\r\n  updateReservationStatus(\r\n    userId: string,\r\n    reservationId: string,\r\n    status: ReservationStatus,\r\n    completedAt?: Date\r\n  ): Promise<void>;\r\n\r\n  // ==================== Atomic Operations ====================\r\n\r\n  /**\r\n   * Reserve credits atomically (creates reservation + updates balance in transaction)\r\n   * @param userId - User ID\r\n   * @param amount - Credits to reserve\r\n   * @param operationType - Operation type for tracking\r\n   * @param expiresAt - Reservation expiry time\r\n   * @returns Created reservation\r\n   * @throws Error if insufficient credits\r\n   */\r\n  reserveCreditsAtomic(\r\n    userId: string,\r\n    amount: number,\r\n    operationType: CreditOperationType,\r\n    expiresAt: Date\r\n  ): Promise<PortableReservation>;\r\n\r\n  /**\r\n   * Commit a reservation atomically (deducts credits + marks reservation committed)\r\n   * @param userId - User ID\r\n   * @param reservationId - Reservation ID\r\n   * @throws Error if reservation not found or not in reserved state\r\n   */\r\n  commitReservationAtomic(userId: string, reservationId: string): Promise<void>;\r\n\r\n  /**\r\n   * Release a reservation atomically (releases reserved credits + marks reservation released)\r\n   * @param userId - User ID\r\n   * @param reservationId - Reservation ID\r\n   */\r\n  releaseReservationAtomic(userId: string, reservationId: string): Promise<void>;\r\n\r\n  /**\r\n   * Add credits atomically (creates transaction + updates balance)\r\n   * @param userId - User ID\r\n   * @param amount - Credits to add\r\n   * @param description - Transaction description\r\n   * @param paymentRef - Optional payment reference\r\n   */\r\n  addCreditsAtomic(\r\n    userId: string,\r\n    amount: number,\r\n    description: string,\r\n    paymentRef?: string\r\n  ): Promise<void>;\r\n\r\n  /**\r\n   * Deduct credits from a user's balance atomically.\r\n   *\r\n   * Splits the deduction across `balance` and `bonusCredits`, draining\r\n   * `balance` (monthly, resets each cycle) first so persistent bonus\r\n   * credits survive longer. Runs in a single atomic transaction so\r\n   * concurrent deducts cannot drive either field negative.\r\n   *\r\n   * Callers are responsible for writing any audit record (journal /\r\n   * transaction) — this method only moves credits.\r\n   *\r\n   * @param userId - User ID\r\n   * @param amount - Credits to deduct (positive)\r\n   * @returns Combined totals (balance + bonusCredits) before and after.\r\n   * @throws If user has no credits doc or insufficient credits\r\n   */\r\n  deductCreditsAtomic(\r\n    userId: string,\r\n    amount: number\r\n  ): Promise<{ previousBalance: number; newBalance: number }>;\r\n\r\n  // ==================== Transactions ====================\r\n\r\n  /**\r\n   * Create a credit transaction record\r\n   * @param input - Transaction data\r\n   * @returns Created transaction\r\n   */\r\n  createTransaction(input: CreateTransactionInput): Promise<PortableTransaction>;\r\n\r\n  /**\r\n   * Get user's transaction history\r\n   * @param userId - User ID\r\n   * @param limit - Max results\r\n   * @param offset - Skip results\r\n   * @returns List of transactions\r\n   */\r\n  getTransactions(\r\n    userId: string,\r\n    limit?: number,\r\n    offset?: number\r\n  ): Promise<PortableTransaction[]>;\r\n\r\n  // ==================== Usage Logs ====================\r\n\r\n  /**\r\n   * Log a usage event\r\n   * @param input - Usage log data\r\n   * @returns Created usage log\r\n   */\r\n  logUsage(input: CreateUsageLogInput): Promise<PortableUsageLog>;\r\n\r\n  /**\r\n   * Query usage logs\r\n   * @param query - Query parameters\r\n   * @returns List of usage logs\r\n   */\r\n  getUsageLogs(query: UsageLogQuery): Promise<PortableUsageLog[]>;\r\n\r\n  /**\r\n   * Get usage log count (for pagination)\r\n   * @param query - Query parameters (without limit/offset)\r\n   * @returns Count of matching logs\r\n   */\r\n  getUsageLogsCount(query: Omit<UsageLogQuery, \"limit\" | \"offset\">): Promise<number>;\r\n\r\n  // ==================== Cleanup Operations ====================\r\n\r\n  /**\r\n   * Find and expire reservations past their expiration time\r\n   * Used by cron job to clean up stale reservations\r\n   * @param batchSize - Maximum number of reservations to process per batch (default: 100)\r\n   * @param maxIterations - Maximum number of pagination iterations to prevent infinite loops (default: 100)\r\n   * @returns Cleanup results with counts and errors\r\n   */\r\n  findAndExpireReservations(batchSize?: number, maxIterations?: number): Promise<{\r\n    expiredCount: number;\r\n    creditsReleased: number;\r\n    errors: string[];\r\n  }>;\r\n\r\n  // ==================== Atomic Monthly Reset ====================\r\n\r\n  /**\r\n   * Atomically perform monthly reset if needed\r\n   * Uses optimistic locking to prevent race conditions\r\n   *\r\n   * @param userId - User ID\r\n   * @param tier - User's current subscription tier (for determining new balance)\r\n   * @param expectedResetAt - The expected monthlyResetAt value (for optimistic locking)\r\n   * @returns Result indicating whether reset was performed and updated credits\r\n   */\r\n  atomicMonthlyReset(\r\n    userId: string,\r\n    tier: SubscriptionTier,\r\n    expectedResetAt: Date | string\r\n  ): Promise<MonthlyResetResult>;\r\n\r\n  // ==================== Subscription Expiry ====================\r\n\r\n  /**\r\n   * Check and handle subscription expiry with grace period\r\n   * Auto-downgrades expired subscriptions after grace period\r\n   *\r\n   * @param userId - User ID\r\n   * @param gracePeriodDays - Days to allow after expiry before downgrade (default: 3)\r\n   * @returns Result indicating whether downgrade occurred\r\n   */\r\n  checkAndHandleSubscriptionExpiry(\r\n    userId: string,\r\n    gracePeriodDays?: number\r\n  ): Promise<SubscriptionExpiryResult>;\r\n\r\n  // ==================== Journal Entries ====================\r\n\r\n  /**\r\n   * Create a journal entry for audit trail\r\n   * @param input - Journal entry data\r\n   * @returns Created journal entry\r\n   */\r\n  createJournalEntry(input: CreateJournalEntryInput): Promise<PortableJournalEntry>;\r\n\r\n  /**\r\n   * Get journal entries for a user\r\n   * @param query - Query parameters\r\n   * @returns List of journal entries\r\n   */\r\n  getJournalEntries(query: JournalEntryQuery): Promise<PortableJournalEntry[]>;\r\n\r\n  /**\r\n   * Get journal entry count for pagination\r\n   * @param query - Query parameters (without limit/offset)\r\n   * @returns Count of matching entries\r\n   */\r\n  getJournalEntriesCount(query: Omit<JournalEntryQuery, \"limit\" | \"offset\">): Promise<number>;\r\n}\r\n\r\n/**\r\n * Factory type for creating repository instances\r\n */\r\nexport type CreditRepositoryFactory = () => ICreditRepository;\r\n\r\n/**\r\n * Convert PortableUserCredits to client-safe format\r\n * Utility function that implementations can use\r\n */\r\nexport function toClientUserCredits(credits: PortableUserCredits): PortableUserCredits {\r\n  // Already in portable format, just ensure all timestamps are ISO strings\r\n  return {\r\n    userId: credits.userId,\r\n    balance: credits.balance,\r\n    bonusCredits: credits.bonusCredits ?? 0,\r\n    reserved: credits.reserved,\r\n    tier: credits.tier,\r\n    monthlyLimit: credits.monthlyLimit,\r\n    monthlyUsed: credits.monthlyUsed,\r\n    monthlyResetAt: toISOString(credits.monthlyResetAt),\r\n    subscriptionExpiresAt: credits.subscriptionExpiresAt\r\n      ? toISOString(credits.subscriptionExpiresAt)\r\n      : null,\r\n    createdAt: toISOString(credits.createdAt),\r\n    updatedAt: toISOString(credits.updatedAt),\r\n  };\r\n}\r\n\r\n/**\r\n * Convert any timestamp-like value to ISO string\r\n */\r\nfunction toISOString(value: unknown): string {\r\n  if (!value) return new Date().toISOString();\r\n  if (typeof value === \"string\") return value;\r\n  if (value instanceof Date) return value.toISOString();\r\n  // Handle Firestore Timestamp-like objects\r\n  if (typeof value === \"object\" && \"toDate\" in value && typeof (value as { toDate: () => Date }).toDate === \"function\") {\r\n    return (value as { toDate: () => Date }).toDate().toISOString();\r\n  }\r\n  return new Date().toISOString();\r\n}\r\n","import { z } from \"zod\";\r\nimport type { SubscriptionTier, TierConfig } from \"../core/types.js\";\r\nimport { getConfigProvider } from \"./provider.js\";\r\n\r\n/**\r\n * Schema for tier configuration\r\n */\r\nconst tierConfigSchema = z.object({\r\n  tier: z.string().min(1),\r\n  monthlyCredits: z.number().min(0),\r\n  priceUsd: z.number().min(0),\r\n  features: z.array(z.string()),\r\n  isFree: z.boolean().optional(),\r\n  unlimited: z.boolean().optional(),\r\n  isDefault: z.boolean().optional(),\r\n});\r\n\r\n/**\r\n * Schema for credit system configuration\r\n */\r\nconst creditSystemConfigSchema = z.object({\r\n  /**\r\n   * Operation costs in credits\r\n   * Keys are operation type names (any non-empty string)\r\n   * Values are positive numbers representing credit cost\r\n   */\r\n  operationCosts: z.record(\r\n    z.string().min(1),\r\n    z.number().positive()\r\n  ),\r\n\r\n  /**\r\n   * Display labels for operation types (English canonical labels).\r\n   * Falls back to auto-generating from snake_case keys if not provided.\r\n   */\r\n  operationLabels: z.record(\r\n    z.string().min(1),\r\n    z.string().min(1)\r\n  ).optional().default({}),\r\n\r\n  /**\r\n   * Tier configurations\r\n   */\r\n  tierConfigs: z.record(\r\n    z.string().min(1),\r\n    tierConfigSchema\r\n  ),\r\n\r\n  /**\r\n   * Reservation expiry time in milliseconds\r\n   */\r\n  reservationExpiryMs: z.number().positive().default(5 * 60 * 1000),\r\n\r\n  /**\r\n   * Default credits for new users (free tier)\r\n   */\r\n  defaultFreeCredits: z.number().positive().default(25),\r\n\r\n  /**\r\n   * Grace period in days after subscription expires before downgrade\r\n   */\r\n  subscriptionGracePeriodDays: z.number().min(0).default(3),\r\n\r\n  /**\r\n   * Low balance notification threshold\r\n   */\r\n  lowBalanceThreshold: z.number().min(0).default(10),\r\n\r\n  /**\r\n   * Cooldown in hours between low balance notifications\r\n   */\r\n  lowBalanceNotificationCooldownHours: z.number().positive().default(24),\r\n\r\n  /**\r\n   * Feature flags for optional features\r\n   */\r\n  features: z.object({\r\n    journalEntries: z.boolean().default(true),\r\n    notifications: z.boolean().default(true),\r\n    subscriptionExpiry: z.boolean().default(true),\r\n    usageHistory: z.boolean().default(true),\r\n  }).default({\r\n    journalEntries: true,\r\n    notifications: true,\r\n    subscriptionExpiry: true,\r\n    usageHistory: true,\r\n  }),\r\n});\r\n\r\n/**\r\n * Credit system configuration type\r\n */\r\nexport type CreditSystemConfig = z.infer<typeof creditSystemConfigSchema>;\r\n\r\n/**\r\n * Default configuration\r\n */\r\nconst DEFAULT_CONFIG: CreditSystemConfig = {\r\n  operationCosts: {\r\n    story_generation: 5,\r\n    conversation: 2,\r\n    image_generation: 10,\r\n    template_generation: 10,\r\n  },\r\n  operationLabels: {\r\n    story_generation: \"Story generation\",\r\n    conversation: \"Conversation\",\r\n    image_generation: \"Image generation\",\r\n    template_generation: \"Template generation\",\r\n  },\r\n  tierConfigs: {\r\n    free: {\r\n      tier: \"free\",\r\n      monthlyCredits: 25,\r\n      priceUsd: 0,\r\n      isFree: true,\r\n      isDefault: true,\r\n      features: [\r\n        \"25 credits per month\",\r\n        \"Basic story generation\",\r\n        \"Standard image quality\",\r\n      ],\r\n    },\r\n    basic: {\r\n      tier: \"basic\",\r\n      monthlyCredits: 200,\r\n      priceUsd: 9.99,\r\n      features: [\r\n        \"200 credits per month\",\r\n        \"All story features\",\r\n        \"High quality images\",\r\n        \"Priority support\",\r\n      ],\r\n    },\r\n    premium: {\r\n      tier: \"premium\",\r\n      monthlyCredits: 500,\r\n      priceUsd: 19.99,\r\n      features: [\r\n        \"500 credits per month\",\r\n        \"All features\",\r\n        \"Highest quality images\",\r\n        \"Early access to new features\",\r\n        \"Priority support\",\r\n      ],\r\n    },\r\n    unlimited: {\r\n      tier: \"unlimited\",\r\n      monthlyCredits: 0, // 0 = unlimited\r\n      unlimited: true,\r\n      priceUsd: 49.99,\r\n      features: [\r\n        \"Unlimited credits\",\r\n        \"All features\",\r\n        \"Highest quality images\",\r\n        \"Early access to new features\",\r\n        \"Dedicated support\",\r\n      ],\r\n    },\r\n  },\r\n  reservationExpiryMs: 5 * 60 * 1000, // 5 minutes\r\n  defaultFreeCredits: 25,\r\n  subscriptionGracePeriodDays: 3,\r\n  lowBalanceThreshold: 10,\r\n  lowBalanceNotificationCooldownHours: 24,\r\n  features: {\r\n    journalEntries: true,\r\n    notifications: true,\r\n    subscriptionExpiry: true,\r\n    usageHistory: true,\r\n  },\r\n};\r\n\r\n/**\r\n * Current configuration (can be overridden)\r\n */\r\nlet currentConfig: CreditSystemConfig = DEFAULT_CONFIG;\r\n\r\n/**\r\n * Load configuration from environment variables\r\n * Can be extended to load from other sources (file, remote config, etc.)\r\n *\r\n * Operation costs are loaded from CREDITS_OPERATION_COSTS as JSON:\r\n * CREDITS_OPERATION_COSTS={\"story_generation\":5,\"image_generation\":10}\r\n */\r\nexport function loadConfigFromEnv(): Partial<CreditSystemConfig> {\r\n  const envConfig: Partial<CreditSystemConfig> = {};\r\n\r\n  // Load operation costs from env as JSON\r\n  const operationCostsEnv = process.env.CREDITS_OPERATION_COSTS;\r\n  if (operationCostsEnv) {\r\n    try {\r\n      const parsed = JSON.parse(operationCostsEnv);\r\n      if (typeof parsed === \"object\" && parsed !== null) {\r\n        envConfig.operationCosts = {\r\n          ...DEFAULT_CONFIG.operationCosts,\r\n          ...parsed,\r\n        };\r\n      }\r\n    } catch {\r\n      console.warn(\"[Credits Config] Failed to parse CREDITS_OPERATION_COSTS env var as JSON\");\r\n    }\r\n  }\r\n\r\n  // Load other settings from env\r\n  const reservationExpiry = process.env.CREDITS_RESERVATION_EXPIRY_MS;\r\n  if (reservationExpiry) {\r\n    envConfig.reservationExpiryMs = parseInt(reservationExpiry, 10);\r\n  }\r\n\r\n  const defaultCredits = process.env.CREDITS_DEFAULT_FREE;\r\n  if (defaultCredits) {\r\n    envConfig.defaultFreeCredits = parseInt(defaultCredits, 10);\r\n  }\r\n\r\n  const gracePeriod = process.env.CREDITS_GRACE_PERIOD_DAYS;\r\n  if (gracePeriod) {\r\n    envConfig.subscriptionGracePeriodDays = parseInt(gracePeriod, 10);\r\n  }\r\n\r\n  const lowBalanceThreshold = process.env.CREDITS_LOW_BALANCE_THRESHOLD;\r\n  if (lowBalanceThreshold) {\r\n    envConfig.lowBalanceThreshold = parseInt(lowBalanceThreshold, 10);\r\n  }\r\n\r\n  // Load feature flags\r\n  const featuresEnv = process.env.CREDITS_FEATURES;\r\n  if (featuresEnv) {\r\n    try {\r\n      const features = JSON.parse(featuresEnv);\r\n      envConfig.features = {\r\n        ...DEFAULT_CONFIG.features,\r\n        ...features,\r\n      };\r\n    } catch {\r\n      console.warn(\"[Credits Config] Failed to parse CREDITS_FEATURES env var\");\r\n    }\r\n  }\r\n\r\n  return envConfig;\r\n}\r\n\r\n/**\r\n * Initialize configuration\r\n * Merges default config with environment overrides\r\n */\r\nexport function initializeConfig(overrides?: Partial<CreditSystemConfig>): CreditSystemConfig {\r\n  const envConfig = loadConfigFromEnv();\r\n\r\n  const mergedConfig = {\r\n    ...DEFAULT_CONFIG,\r\n    ...envConfig,\r\n    ...overrides,\r\n    operationCosts: {\r\n      ...DEFAULT_CONFIG.operationCosts,\r\n      ...envConfig.operationCosts,\r\n      ...overrides?.operationCosts,\r\n    },\r\n    operationLabels: {\r\n      ...DEFAULT_CONFIG.operationLabels,\r\n      ...envConfig.operationLabels,\r\n      ...overrides?.operationLabels,\r\n    },\r\n    tierConfigs: {\r\n      ...DEFAULT_CONFIG.tierConfigs,\r\n      ...envConfig.tierConfigs,\r\n      ...overrides?.tierConfigs,\r\n    },\r\n    features: {\r\n      ...DEFAULT_CONFIG.features,\r\n      ...envConfig.features,\r\n      ...overrides?.features,\r\n    },\r\n  };\r\n\r\n  // Validate configuration\r\n  const result = creditSystemConfigSchema.safeParse(mergedConfig);\r\n  if (!result.success) {\r\n    console.error(\"[Credits Config] Invalid configuration:\", result.error.issues);\r\n    throw new Error(\"Invalid credit system configuration\");\r\n  }\r\n\r\n  currentConfig = result.data;\r\n  return currentConfig;\r\n}\r\n\r\n/**\r\n * Get the current configuration.\r\n * If an external provider is registered, delegates to it.\r\n * Otherwise falls back to the local currentConfig.\r\n */\r\nexport function getConfig(): CreditSystemConfig {\r\n  const provider = getConfigProvider();\r\n  if (provider) {\r\n    return provider.getConfig();\r\n  }\r\n  return currentConfig;\r\n}\r\n\r\n/**\r\n * Get all valid operation types from configuration\r\n * Returns the keys of the operationCosts object\r\n */\r\nexport function getValidOperationTypes(): string[] {\r\n  return Object.keys(getConfig().operationCosts);\r\n}\r\n\r\n/**\r\n * Check if an operation type is valid (configured in the system)\r\n * @param type - The operation type to check\r\n * @returns true if the operation type is configured\r\n */\r\nexport function isValidOperationType(type: string): boolean {\r\n  return type in getConfig().operationCosts;\r\n}\r\n\r\n/**\r\n * Get operation cost from configuration\r\n * @throws Error if the operation type is not configured\r\n */\r\nexport function getConfigOperationCost(operationType: string): number {\r\n  const cost = getConfig().operationCosts[operationType];\r\n  if (cost === undefined) {\r\n    throw new Error(\r\n      `Unknown operation type: ${operationType}. Valid types: ${getValidOperationTypes().join(\", \")}`\r\n    );\r\n  }\r\n  return cost;\r\n}\r\n\r\n/**\r\n * Get all valid tier ids from configuration\r\n * Returns the keys of the tierConfigs object\r\n */\r\nexport function getValidTiers(): string[] {\r\n  return Object.keys(getConfig().tierConfigs);\r\n}\r\n\r\n/**\r\n * Check if a tier id is valid (configured in the system)\r\n */\r\nexport function isValidTier(tier: string): boolean {\r\n  return tier in getConfig().tierConfigs;\r\n}\r\n\r\n/**\r\n * Check if a tier is the free/default tier.\r\n * Falls back to priceUsd === 0 when the isFree flag is not set.\r\n */\r\nexport function isFreeTier(tier: SubscriptionTier): boolean {\r\n  const c = getConfig().tierConfigs[tier];\r\n  if (!c) return false;\r\n  return c.isFree ?? c.priceUsd === 0;\r\n}\r\n\r\n/**\r\n * Check if a tier is unlimited.\r\n * Falls back to monthlyCredits === 0 when the unlimited flag is not set.\r\n */\r\nexport function isUnlimitedTier(tier: SubscriptionTier): boolean {\r\n  const c = getConfig().tierConfigs[tier];\r\n  if (!c) return false;\r\n  return c.unlimited ?? c.monthlyCredits === 0;\r\n}\r\n\r\n/**\r\n * Get the tier assigned to brand-new users.\r\n * Prefers the tier flagged isDefault, then the first isFree tier, then \"free\".\r\n */\r\nexport function getDefaultTier(): SubscriptionTier {\r\n  const entries = Object.entries(getConfig().tierConfigs);\r\n  const def =\r\n    entries.find(([, c]) => c.isDefault)?.[0] ??\r\n    entries.find(([, c]) => c.isFree ?? c.priceUsd === 0)?.[0];\r\n  return def ?? \"free\";\r\n}\r\n\r\n/**\r\n * Magic balance assigned to unlimited tiers on upgrade.\r\n * Kept at 999999 for back-compat with stored/displayed balances.\r\n */\r\nexport const UNLIMITED_BALANCE_SENTINEL = 999999;\r\n\r\n/**\r\n * Get the magic balance assigned to unlimited tiers on upgrade.\r\n */\r\nexport function getUnlimitedSentinelBalance(): number {\r\n  return UNLIMITED_BALANCE_SENTINEL;\r\n}\r\n\r\n/**\r\n * Get tier configuration from configuration.\r\n * Falls back to the default tier (with a warning) if the tier is unknown.\r\n */\r\nexport function getConfigTierConfig(tier: SubscriptionTier): TierConfig {\r\n  const cfg = getConfig().tierConfigs[tier];\r\n  if (cfg) return cfg;\r\n  const fallback = getDefaultTier();\r\n  console.warn(\r\n    `[Credits Config] Unknown tier \"${tier}\". Falling back to \"${fallback}\". Valid: ${getValidTiers().join(\", \")}`\r\n  );\r\n  return getConfig().tierConfigs[fallback]!;\r\n}\r\n\r\n/**\r\n * Get monthly limit for a tier from configuration\r\n * Returns Infinity for unlimited tier\r\n */\r\nexport function getConfigMonthlyLimit(tier: SubscriptionTier): number {\r\n  return isUnlimitedTier(tier) ? Infinity : getConfigTierConfig(tier).monthlyCredits;\r\n}\r\n\r\n/**\r\n * Runtime zod validator for tier ids. Validates against the LIVE config keys\r\n * at parse time, so apps that add tiers via config get correct validation.\r\n */\r\nexport const tierSchema = z\r\n  .string()\r\n  .refine(isValidTier, { message: \"Unknown subscription tier\" }) as unknown as z.ZodType<SubscriptionTier>;\r\n\r\n/**\r\n * Parse/validate a tier id against the live config keys.\r\n * @throws ZodError if the tier is not configured\r\n */\r\nexport function parseTier(value: unknown): SubscriptionTier {\r\n  return tierSchema.parse(value);\r\n}\r\n\r\n/**\r\n * Check if a feature is enabled\r\n */\r\nexport function isFeatureEnabled(feature: keyof CreditSystemConfig[\"features\"]): boolean {\r\n  return getConfig().features[feature];\r\n}\r\n\r\n/**\r\n * Convert a snake_case string to Title Case.\r\n * E.g., \"story_generation\" -> \"Story Generation\"\r\n */\r\nfunction snakeCaseToTitleCase(str: string): string {\r\n  return str\r\n    .replace(/_/g, \" \")\r\n    .replace(/\\b\\w/g, (c) => c.toUpperCase());\r\n}\r\n\r\n/**\r\n * Get the display label for an operation type.\r\n * Falls back to converting snake_case to Title Case if no label is configured.\r\n */\r\nexport function getOperationLabel(operationType: string): string {\r\n  const labels = getConfig().operationLabels;\r\n  if (labels && operationType in labels) {\r\n    return labels[operationType];\r\n  }\r\n  return snakeCaseToTitleCase(operationType);\r\n}\r\n\r\n/**\r\n * Get all operation labels as a record.\r\n * For any operation in operationCosts that lacks a label, generates one from the key.\r\n */\r\nexport function getOperationLabels(): Record<string, string> {\r\n  const config = getConfig();\r\n  const result: Record<string, string> = {};\r\n  for (const key of Object.keys(config.operationCosts)) {\r\n    result[key] = config.operationLabels?.[key]\r\n      ?? snakeCaseToTitleCase(key);\r\n  }\r\n  return result;\r\n}\r\n\r\n/**\r\n * Reset configuration to defaults (for testing)\r\n */\r\nexport function resetConfig(): void {\r\n  currentConfig = DEFAULT_CONFIG;\r\n}\r\n\r\n// Initialize configuration on module load\r\ninitializeConfig();\r\n","import type { CreditSystemConfig } from \"./index.js\";\r\n\r\n/** Interface for external config providers (e.g. Firestore-backed) */\r\nexport interface ICreditConfigProvider {\r\n  getConfig(): CreditSystemConfig;\r\n}\r\n\r\nlet configProvider: ICreditConfigProvider | null = null;\r\n\r\n/** Register an external config provider. Called once at app startup. */\r\nexport function registerConfigProvider(provider: ICreditConfigProvider): void {\r\n  configProvider = provider;\r\n}\r\n\r\n/** Get the registered provider, or null if none registered. */\r\nexport function getConfigProvider(): ICreditConfigProvider | null {\r\n  return configProvider;\r\n}\r\n\r\n/** Clear registered provider (for testing). */\r\nexport function clearConfigProvider(): void {\r\n  configProvider = null;\r\n}\r\n","import type {\r\n  PortableUserCredits,\r\n  PortableReservation,\r\n  CreditCheckResult,\r\n  CreditOperationType,\r\n  SubscriptionTier,\r\n  PortableUsageLog,\r\n  PortableJournalEntry,\r\n  UsageHistoryEntry,\r\n  UsageHistoryResponse,\r\n} from \"../core/types.js\";\r\nimport { toDate } from \"../core/types.js\";\r\nimport type { ICreditRepository, CreateUsageLogInput, JournalEntryQuery } from \"../repository/types.js\";\r\nimport { toClientUserCredits } from \"../repository/types.js\";\r\nimport {\r\n  getConfig,\r\n  getConfigMonthlyLimit,\r\n  getOperationLabel,\r\n  isFreeTier,\r\n  isUnlimitedTier,\r\n  getDefaultTier,\r\n  getUnlimitedSentinelBalance,\r\n} from \"../config/index.js\";\r\n\r\n/**\r\n * Check if a date is past the monthly reset date\r\n */\r\nfunction isPastMonthlyReset(resetAt: unknown): boolean {\r\n  if (!resetAt) return false;\r\n  const resetDate = toDate(resetAt);\r\n  return new Date() >= resetDate;\r\n}\r\n\r\n/**\r\n * Notification callback type for low balance notifications\r\n */\r\nexport type LowBalanceNotificationCallback = (userId: string, balance: number) => Promise<void>;\r\n\r\n/**\r\n * Notification callback type for subscription expired notifications\r\n */\r\nexport type SubscriptionExpiredNotificationCallback = (userId: string, wasDowngraded: boolean) => Promise<void>;\r\n\r\n/**\r\n * Options for reserving credits.\r\n */\r\nexport interface ReserveCreditsOptions {\r\n  /**\r\n   * Time-to-live for the reservation, in milliseconds, before the cleanup\r\n   * sweep may expire it. Defaults to `config.reservationExpiryMs` (suitable\r\n   * for synchronous, in-request operations). Long-running async jobs\r\n   * (e.g. background media generation) should pass a TTL matching their\r\n   * own lifecycle so the reservation is not expired while the job is still\r\n   * legitimately in flight.\r\n   */\r\n  ttlMs?: number;\r\n}\r\n\r\n/**\r\n * Credits service with dependency injection for repository\r\n *\r\n * Provides business logic for credit operations, delegating\r\n * database operations to the injected repository.\r\n */\r\nexport class CreditsService {\r\n  private lowBalanceCallback?: LowBalanceNotificationCallback;\r\n  private subscriptionExpiredCallback?: SubscriptionExpiredNotificationCallback;\r\n\r\n  constructor(private readonly repository: ICreditRepository) {}\r\n\r\n  /**\r\n   * Set callback for low balance notifications\r\n   */\r\n  setLowBalanceCallback(callback: LowBalanceNotificationCallback): void {\r\n    this.lowBalanceCallback = callback;\r\n  }\r\n\r\n  /**\r\n   * Set callback for subscription expired notifications\r\n   */\r\n  setSubscriptionExpiredCallback(callback: SubscriptionExpiredNotificationCallback): void {\r\n    this.subscriptionExpiredCallback = callback;\r\n  }\r\n\r\n  /**\r\n   * Get user credits, performing monthly reset and subscription expiry checks if needed\r\n   *\r\n   * This method uses atomic operations to prevent race conditions:\r\n   * 1. Checks subscription expiry with grace period\r\n   * 2. Atomically performs monthly reset if needed (with optimistic locking)\r\n   *\r\n   * @param userId - User ID\r\n   * @returns User credits or null if not found\r\n   */\r\n  async getUserCredits(userId: string): Promise<PortableUserCredits | null> {\r\n    let data = await this.repository.getUserCredits(userId);\r\n\r\n    if (!data) {\r\n      return null;\r\n    }\r\n\r\n    // Step 1: Check subscription expiry (for non-free tiers)\r\n    if (!isFreeTier(data.tier) && data.subscriptionExpiresAt) {\r\n      const expiryResult = await this.repository.checkAndHandleSubscriptionExpiry(\r\n        userId,\r\n        getConfig().subscriptionGracePeriodDays\r\n      );\r\n\r\n      if (expiryResult.wasDowngraded) {\r\n        // Create journal entry for downgrade\r\n        await this.repository.createJournalEntry({\r\n          userId,\r\n          entryType: \"debit\",\r\n          amount: 0, // No credits deducted, just tier change\r\n          balanceAfter: expiryResult.credits.balance,\r\n          source: \"subscription_downgrade\",\r\n          referenceId: `downgrade-${Date.now()}`,\r\n          referenceType: \"subscription\",\r\n          description: `Subscription expired. Downgraded from ${data.tier} to free tier.`,\r\n          metadata: {\r\n            previousTier: data.tier,\r\n            previousBalance: data.balance,\r\n            newBalance: expiryResult.credits.balance,\r\n          },\r\n        });\r\n\r\n        // Trigger subscription expired notification (non-blocking)\r\n        if (this.subscriptionExpiredCallback) {\r\n          this.subscriptionExpiredCallback(userId, true).catch((error) => {\r\n            console.error(\"[Credits] Failed to send subscription expired notification:\", error);\r\n          });\r\n        }\r\n      }\r\n\r\n      // Use the potentially updated credits\r\n      data = expiryResult.credits;\r\n    }\r\n\r\n    // Step 2: Check if monthly reset is needed (use atomic operation)\r\n    if (isPastMonthlyReset(data.monthlyResetAt)) {\r\n      // Convert monthlyResetAt to a compatible type (Date or string)\r\n      const expectedResetAt = toDate(data.monthlyResetAt);\r\n      const resetResult = await this.repository.atomicMonthlyReset(\r\n        userId,\r\n        data.tier,\r\n        expectedResetAt\r\n      );\r\n\r\n      if (resetResult.wasReset) {\r\n        // Create journal entry for monthly reset\r\n        const balanceChange = resetResult.credits.balance - data.balance;\r\n        if (balanceChange !== 0) {\r\n          await this.repository.createJournalEntry({\r\n            userId,\r\n            entryType: balanceChange > 0 ? \"credit\" : \"debit\",\r\n            amount: Math.abs(balanceChange),\r\n            balanceAfter: resetResult.credits.balance,\r\n            source: \"monthly_reset\",\r\n            referenceId: `reset-${Date.now()}`,\r\n            referenceType: \"reset\",\r\n            description: `Monthly credit reset for ${data.tier} tier.`,\r\n            metadata: {\r\n              tier: data.tier,\r\n              previousBalance: data.balance,\r\n              newBalance: resetResult.credits.balance,\r\n            },\r\n          });\r\n        }\r\n      }\r\n\r\n      // Use the potentially updated credits\r\n      data = resetResult.credits;\r\n    }\r\n\r\n    return toClientUserCredits(data);\r\n  }\r\n\r\n  /**\r\n   * Initialize credits for a new user with free tier\r\n   * @param userId - User ID\r\n   * @returns Initialized user credits\r\n   */\r\n  async initializeUserCredits(userId: string): Promise<PortableUserCredits> {\r\n    const credits = await this.repository.initializeUserCredits(\r\n      userId,\r\n      getDefaultTier(),\r\n      getConfig().defaultFreeCredits\r\n    );\r\n    return toClientUserCredits(credits);\r\n  }\r\n\r\n  /**\r\n   * Get or create user credits\r\n   * Initializes with free tier if not exists\r\n   * @param userId - User ID\r\n   * @returns User credits\r\n   */\r\n  async getOrCreateUserCredits(userId: string): Promise<PortableUserCredits> {\r\n    const existing = await this.getUserCredits(userId);\r\n    if (existing) {\r\n      return existing;\r\n    }\r\n    return this.initializeUserCredits(userId);\r\n  }\r\n\r\n  /**\r\n   * Check if user has sufficient credits for an operation\r\n   * @param userId - User ID\r\n   * @param requiredCredits - Credits required\r\n   * @returns Credit check result\r\n   */\r\n  async checkCredits(userId: string, requiredCredits: number): Promise<CreditCheckResult> {\r\n    const credits = await this.getOrCreateUserCredits(userId);\r\n\r\n    // Available = balance + bonusCredits - reserved\r\n    const totalBalance = credits.balance + credits.bonusCredits;\r\n    const available = totalBalance - credits.reserved;\r\n    const hasCredits = available >= requiredCredits;\r\n\r\n    return {\r\n      hasCredits,\r\n      balance: totalBalance,\r\n      required: requiredCredits,\r\n      shortfall: hasCredits ? 0 : requiredCredits - available,\r\n    };\r\n  }\r\n\r\n  /**\r\n   * Reserve credits for an operation (phase 1 of two-phase commit)\r\n   * Creates a reservation and locks the credits\r\n   * @param userId - User ID\r\n   * @param amount - Credits to reserve\r\n   * @param operationType - Operation type for tracking\r\n   * @param options - Optional settings (e.g. a custom `ttlMs` for long-running async jobs)\r\n   * @returns Reservation object\r\n   * @throws Error if insufficient credits\r\n   */\r\n  async reserveCredits(\r\n    userId: string,\r\n    amount: number,\r\n    operationType: CreditOperationType,\r\n    options?: ReserveCreditsOptions\r\n  ): Promise<PortableReservation> {\r\n    const ttlMs = options?.ttlMs ?? getConfig().reservationExpiryMs;\r\n    const expiresAt = new Date(Date.now() + ttlMs);\r\n    return this.repository.reserveCreditsAtomic(userId, amount, operationType, expiresAt);\r\n  }\r\n\r\n  /**\r\n   * Commit a reservation (phase 2 of two-phase commit - success)\r\n   * Deducts credits and marks reservation as committed\r\n   * Also triggers low balance notifications if balance drops below threshold\r\n   */\r\n  async commitCredits(userId: string, reservationId: string): Promise<void> {\r\n    // Get the reservation to know the amount\r\n    const reservation = await this.repository.getReservation(userId, reservationId);\r\n    if (!reservation) {\r\n      throw new Error(`Reservation ${reservationId} not found`);\r\n    }\r\n\r\n    // Idempotent: a re-delivered commit for an already-committed reservation\r\n    // is a no-op. The balance + journal were applied by the first commit; the\r\n    // atomic layer also guards the balance, this just avoids a duplicate\r\n    // journal entry on the happy retry path.\r\n    if (reservation.status === \"committed\") {\r\n      return;\r\n    }\r\n\r\n    // Commit the reservation atomically\r\n    await this.repository.commitReservationAtomic(userId, reservationId);\r\n\r\n    // Create journal entry\r\n    const credits = await this.repository.getUserCredits(userId);\r\n    if (credits) {\r\n      await this.repository.createJournalEntry({\r\n        userId,\r\n        entryType: \"debit\",\r\n        amount: reservation.amount,\r\n        balanceAfter: credits.balance,\r\n        source: \"operation_commit\",\r\n        referenceId: reservationId,\r\n        referenceType: \"reservation\",\r\n        description: `Committed ${reservation.amount} credits for ${getOperationLabel(reservation.operationType)}`,\r\n        metadata: {\r\n          operationType: reservation.operationType,\r\n        },\r\n      });\r\n\r\n      // Trigger low balance notification (non-blocking)\r\n      if (this.lowBalanceCallback) {\r\n        this.lowBalanceCallback(userId, credits.balance).catch((error) => {\r\n          console.error(\"[Credits] Failed to send low balance notification:\", error);\r\n        });\r\n      }\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Release a reservation (phase 2 of two-phase commit - failure)\r\n   * Returns reserved credits and marks reservation as released\r\n   */\r\n  async releaseCredits(userId: string, reservationId: string): Promise<void> {\r\n    // Get the reservation to check its state\r\n    const reservation = await this.repository.getReservation(userId, reservationId);\r\n\r\n    // Release the reservation atomically\r\n    await this.repository.releaseReservationAtomic(userId, reservationId);\r\n\r\n    // Create journal entry only if reservation was in reserved state\r\n    if (reservation?.status === \"reserved\") {\r\n      const credits = await this.repository.getUserCredits(userId);\r\n      if (credits) {\r\n        await this.repository.createJournalEntry({\r\n          userId,\r\n          entryType: \"credit\",\r\n          amount: 0, // No actual credits returned (they were reserved, not spent)\r\n          balanceAfter: credits.balance,\r\n          source: \"operation_release\",\r\n          referenceId: reservationId,\r\n          referenceType: \"reservation\",\r\n          description: `Released ${reservation.amount} reserved credits for ${getOperationLabel(reservation.operationType)}`,\r\n          metadata: {\r\n            operationType: reservation.operationType,\r\n            amount: reservation.amount,\r\n          },\r\n        });\r\n      }\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Log usage for audit trail\r\n   * @param log - Usage log data\r\n   */\r\n  async logUsage(log: Omit<PortableUsageLog, \"id\" | \"createdAt\">): Promise<void> {\r\n    await this.repository.logUsage(log as CreateUsageLogInput);\r\n  }\r\n\r\n  /**\r\n   * Add credits to user account (for purchases, bonuses, etc.)\r\n   * @param userId - User ID\r\n   * @param amount - Credits to add\r\n   * @param description - Transaction description\r\n   * @param paymentRef - Optional payment reference\r\n   */\r\n  async addCredits(\r\n    userId: string,\r\n    amount: number,\r\n    description: string,\r\n    paymentRef?: string\r\n  ): Promise<void> {\r\n    return this.repository.addCreditsAtomic(userId, amount, description, paymentRef);\r\n  }\r\n\r\n  /**\r\n   * Update user subscription tier\r\n   * @param userId - User ID\r\n   * @param tier - New subscription tier\r\n   * @param expiresAt - Subscription expiry date (optional)\r\n   */\r\n  async updateTier(\r\n    userId: string,\r\n    tier: SubscriptionTier,\r\n    expiresAt?: Date\r\n  ): Promise<void> {\r\n    const monthlyLimit = getConfigMonthlyLimit(tier);\r\n    const unlimited = isUnlimitedTier(tier);\r\n    const free = isFreeTier(tier);\r\n\r\n    await this.repository.updateUserTier(userId, {\r\n      tier,\r\n      // Stored monthlyLimit uses the 0-means-unlimited convention.\r\n      monthlyLimit: unlimited ? 0 : monthlyLimit,\r\n      // Reset balance to new tier limit if upgrading\r\n      balance: !free\r\n        ? (unlimited ? getUnlimitedSentinelBalance() : monthlyLimit)\r\n        : undefined,\r\n      monthlyUsed: !free ? 0 : undefined,\r\n      subscriptionExpiresAt: expiresAt ? expiresAt.toISOString() : null,\r\n    });\r\n  }\r\n\r\n  /**\r\n   * Get usage logs with optional filtering\r\n   * @param userId - Optional user ID filter\r\n   * @param limit - Max results\r\n   * @param offset - Skip results\r\n   * @returns List of usage logs\r\n   */\r\n  async getUsageLogs(\r\n    userId?: string,\r\n    limit = 50,\r\n    offset = 0\r\n  ): Promise<PortableUsageLog[]> {\r\n    return this.repository.getUsageLogs({ userId, limit, offset });\r\n  }\r\n\r\n  /**\r\n   * Get user-friendly usage history\r\n   * Combines journal entries into a user-facing format\r\n   *\r\n   * @param userId - User ID\r\n   * @param limit - Max results per page\r\n   * @param offset - Skip results for pagination\r\n   * @returns Paginated usage history response\r\n   */\r\n  async getUsageHistory(\r\n    userId: string,\r\n    limit = 20,\r\n    offset = 0\r\n  ): Promise<UsageHistoryResponse> {\r\n    // Get journal entries\r\n    const [entries, total] = await Promise.all([\r\n      this.repository.getJournalEntries({ userId, limit, offset }),\r\n      this.repository.getJournalEntriesCount({ userId }),\r\n    ]);\r\n\r\n    // Convert journal entries to user-friendly format\r\n    const historyEntries: UsageHistoryEntry[] = entries.map((entry) => {\r\n      const type = this.mapSourceToHistoryType(entry.source);\r\n      const creditsChange = entry.entryType === \"credit\" ? entry.amount : -entry.amount;\r\n\r\n      return {\r\n        id: entry.id,\r\n        type,\r\n        creditsChange,\r\n        balanceAfter: entry.balanceAfter,\r\n        description: entry.description,\r\n        createdAt: typeof entry.createdAt === \"string\"\r\n          ? entry.createdAt\r\n          : toDate(entry.createdAt).toISOString(),\r\n      };\r\n    });\r\n\r\n    return {\r\n      entries: historyEntries,\r\n      pagination: {\r\n        total,\r\n        limit,\r\n        offset,\r\n        hasMore: offset + entries.length < total,\r\n      },\r\n    };\r\n  }\r\n\r\n  /**\r\n   * Map journal entry source to user-friendly history type\r\n   */\r\n  private mapSourceToHistoryType(\r\n    source: PortableJournalEntry[\"source\"]\r\n  ): UsageHistoryEntry[\"type\"] {\r\n    switch (source) {\r\n      case \"operation_commit\":\r\n      case \"reservation_expired\":\r\n        return \"usage\";\r\n      case \"purchase\":\r\n        return \"purchase\";\r\n      case \"subscription_grant\":\r\n      case \"subscription_upgrade\":\r\n      case \"bonus\":\r\n        return \"bonus\";\r\n      case \"monthly_reset\":\r\n        return \"reset\";\r\n      case \"refund\":\r\n      case \"operation_release\":\r\n        return \"refund\";\r\n      case \"admin_adjustment\":\r\n      case \"subscription_downgrade\":\r\n      case \"expiry\":\r\n      default:\r\n        return \"adjustment\";\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Get journal entries directly (for admin or debugging)\r\n   * @param query - Journal entry query parameters\r\n   * @returns List of journal entries\r\n   */\r\n  async getJournalEntries(query: JournalEntryQuery): Promise<PortableJournalEntry[]> {\r\n    return this.repository.getJournalEntries(query);\r\n  }\r\n\r\n  /**\r\n   * Get the underlying repository (for advanced use cases)\r\n   */\r\n  getRepository(): ICreditRepository {\r\n    return this.repository;\r\n  }\r\n}\r\n\r\n/**\r\n * Create a credits service with a repository\r\n */\r\nexport function createCreditsService(repository: ICreditRepository): CreditsService {\r\n  return new CreditsService(repository);\r\n}\r\n"],"mappings":"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","names":[]}