{"version":3,"sources":["../../src/adapters/index.ts","../../src/config/index.ts","../../src/config/provider.ts","../../src/core/operations.ts","../../src/adapters/generic.ts"],"sourcesContent":["export type {\r\n  ActionResult,\r\n  CreditActionHandler,\r\n  CreditsAdapterConfig,\r\n  CreditsAdapter,\r\n} from \"./types.js\";\r\n\r\nexport { createGenericAdapter } from \"./generic.js\";\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","/**\r\n * Core credit operations - framework agnostic\r\n *\r\n * Contains the business logic for credit operations that can be\r\n * used by any adapter or service implementation.\r\n */\r\n\r\nimport type { ICreditRepository } from \"../repository/types.js\";\r\nimport type { PortableReservation } from \"./types.js\";\r\nimport { getOperationLabel } from \"../config/index.js\";\r\n\r\n/**\r\n * Commit a reservation with journal entry\r\n *\r\n * @param repository - The credit repository\r\n * @param userId - User ID\r\n * @param reservationId - Reservation to commit\r\n */\r\nexport async function commitReservationWithJournal(\r\n  repository: ICreditRepository,\r\n  userId: string,\r\n  reservationId: string\r\n): Promise<void> {\r\n  // Get the reservation to know the amount\r\n  const reservation = await repository.getReservation(userId, reservationId);\r\n  if (!reservation) {\r\n    throw new Error(`Reservation ${reservationId} not found`);\r\n  }\r\n\r\n  // Commit the reservation atomically\r\n  await repository.commitReservationAtomic(userId, reservationId);\r\n\r\n  // Create journal entry\r\n  const credits = await repository.getUserCredits(userId);\r\n  if (credits) {\r\n    await 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}\r\n\r\n/**\r\n * Release a reservation with journal entry\r\n *\r\n * @param repository - The credit repository\r\n * @param userId - User ID\r\n * @param reservationId - Reservation to release\r\n */\r\nexport async function releaseReservationWithJournal(\r\n  repository: ICreditRepository,\r\n  userId: string,\r\n  reservationId: string\r\n): Promise<void> {\r\n  // Get the reservation to check its state\r\n  const reservation = await repository.getReservation(userId, reservationId);\r\n\r\n  // Release the reservation atomically\r\n  await 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 repository.getUserCredits(userId);\r\n    if (credits) {\r\n      await 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 * Reserve credits for an operation\r\n *\r\n * @param repository - The credit repository\r\n * @param userId - User ID\r\n * @param amount - Credits to reserve\r\n * @param operationType - Type of operation\r\n * @param expiryMs - Reservation expiry time in milliseconds\r\n * @returns The reservation\r\n */\r\nexport async function reserveCreditsForOperation(\r\n  repository: ICreditRepository,\r\n  userId: string,\r\n  amount: number,\r\n  operationType: string,\r\n  expiryMs: number = 5 * 60 * 1000\r\n): Promise<PortableReservation> {\r\n  const expiresAt = new Date(Date.now() + expiryMs);\r\n  return repository.reserveCreditsAtomic(userId, amount, operationType, expiresAt);\r\n}\r\n","/**\r\n * Generic credits adapter - framework agnostic\r\n *\r\n * This adapter uses standard JavaScript APIs and works in any\r\n * JavaScript environment (Node.js, browser, etc.).\r\n */\r\n\r\nimport type { PortableReservation, WithCreditsOptions } from \"../core/types.js\";\r\nimport type { ActionResult, CreditsAdapter, CreditsAdapterConfig, CreditActionHandler } from \"./types.js\";\r\nimport {\r\n  commitReservationWithJournal,\r\n  releaseReservationWithJournal,\r\n  reserveCreditsForOperation,\r\n} from \"../core/operations.js\";\r\n\r\n/**\r\n * Generate a unique request ID\r\n */\r\nfunction generateRequestId(): string {\r\n  const timestamp = Date.now();\r\n  const randomPart = Math.random().toString(36).substring(2, 14);\r\n  return `req_${timestamp}_${randomPart}`;\r\n}\r\n\r\n/**\r\n * Create a generic credits adapter\r\n *\r\n * This adapter:\r\n * - Authenticates users via the provided auth provider\r\n * - Reserves credits before action execution\r\n * - Commits credits on success, releases on failure\r\n * - Logs usage asynchronously via the deferred executor\r\n */\r\nexport function createGenericAdapter(config: CreditsAdapterConfig): CreditsAdapter {\r\n  const { repository, authProvider, deferred, operationCosts } = config;\r\n\r\n  function getOperationCost(operationType: string): number {\r\n    const cost = operationCosts[operationType];\r\n    if (cost === undefined) {\r\n      throw new Error(\r\n        `Unknown operation type: \"${operationType}\". ` +\r\n        `Configured types: ${Object.keys(operationCosts).join(\", \")}`\r\n      );\r\n    }\r\n    return cost;\r\n  }\r\n\r\n  function logUsage(params: {\r\n    userId: string;\r\n    operationType: string;\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  }): void {\r\n    deferred.defer(async () => {\r\n      await repository.logUsage({\r\n        userId: params.userId,\r\n        operationType: params.operationType,\r\n        provider: \"gemini\",\r\n        creditsUsed: params.creditsUsed,\r\n        success: params.success,\r\n        errorMessage: params.errorMessage,\r\n        resourceId: params.resourceId,\r\n        resourceType: params.resourceType,\r\n        requestId: params.requestId,\r\n      });\r\n    });\r\n  }\r\n\r\n  return {\r\n    withCredits<TInput, TOutput>(\r\n      options: WithCreditsOptions,\r\n      handler: CreditActionHandler<TInput, TOutput>\r\n    ): (data: TInput) => Promise<ActionResult<TOutput>> {\r\n      return async (data: TInput): Promise<ActionResult<TOutput>> => {\r\n        const requestId = generateRequestId();\r\n\r\n        // Authenticate\r\n        const user = await authProvider.getCurrentUser();\r\n        if (!user?.id) {\r\n          return { success: false, error: \"Authentication required\" };\r\n        }\r\n\r\n        // Handle preview mode - skip credit handling entirely\r\n        if (isPreviewMode(data)) {\r\n          const dummyReservation = createDummyReservation(user.id, options.operationType);\r\n          return handler(user, data, dummyReservation);\r\n        }\r\n\r\n        // Calculate cost and reserve credits\r\n        let reservation: PortableReservation;\r\n        try {\r\n          const cost = options.customCost ?? getOperationCost(options.operationType);\r\n          reservation = await reserveCreditsForOperation(\r\n            repository,\r\n            user.id,\r\n            cost,\r\n            options.operationType\r\n          );\r\n        } catch (error) {\r\n          const message = error instanceof Error ? error.message : \"Failed to reserve credits\";\r\n          logUsage({\r\n            userId: user.id,\r\n            operationType: options.operationType,\r\n            creditsUsed: 0,\r\n            success: false,\r\n            errorMessage: message,\r\n            resourceId: options.resourceId,\r\n            resourceType: options.resourceType,\r\n            requestId,\r\n          });\r\n          return { success: false, error: message };\r\n        }\r\n\r\n        // Execute action\r\n        try {\r\n          const result = await handler(user, data, reservation);\r\n\r\n          if (result.success) {\r\n            await commitReservationWithJournal(repository, user.id, reservation.id);\r\n            logUsage({\r\n              userId: user.id,\r\n              operationType: options.operationType,\r\n              creditsUsed: reservation.amount,\r\n              success: true,\r\n              resourceId: options.resourceId,\r\n              resourceType: options.resourceType,\r\n              requestId,\r\n            });\r\n          } else {\r\n            await releaseReservationWithJournal(repository, user.id, reservation.id);\r\n            logUsage({\r\n              userId: user.id,\r\n              operationType: options.operationType,\r\n              creditsUsed: 0,\r\n              success: false,\r\n              errorMessage: result.error,\r\n              resourceId: options.resourceId,\r\n              resourceType: options.resourceType,\r\n              requestId,\r\n            });\r\n          }\r\n\r\n          return result;\r\n        } catch (error) {\r\n          const message = error instanceof Error ? error.message : \"An unexpected error occurred\";\r\n          await releaseReservationWithJournal(repository, user.id, reservation.id);\r\n          logUsage({\r\n            userId: user.id,\r\n            operationType: options.operationType,\r\n            creditsUsed: 0,\r\n            success: false,\r\n            errorMessage: message,\r\n            resourceId: options.resourceId,\r\n            resourceType: options.resourceType,\r\n            requestId,\r\n          });\r\n          console.error(\"Action error:\", error);\r\n          return { success: false, error: message };\r\n        }\r\n      };\r\n    },\r\n  };\r\n}\r\n\r\nfunction isPreviewMode(data: unknown): boolean {\r\n  return (\r\n    typeof data === \"object\" &&\r\n    data !== null &&\r\n    \"preview\" in data &&\r\n    (data as Record<string, unknown>).preview === true\r\n  );\r\n}\r\n\r\nfunction createDummyReservation(userId: string, operationType: string): PortableReservation {\r\n  return {\r\n    id: \"preview-mode\",\r\n    userId,\r\n    amount: 0,\r\n    operationType,\r\n    status: \"released\",\r\n    createdAt: new Date().toISOString(),\r\n    expiresAt: new Date().toISOString(),\r\n  };\r\n}\r\n"],"mappings":"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","names":[]}