@nehorai/credits 1.3.0 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +153 -153
- package/dist/adapters/index.cjs +13 -3
- package/dist/adapters/index.cjs.map +1 -1
- package/dist/adapters/index.d.cts +2 -2
- package/dist/adapters/index.d.ts +2 -2
- package/dist/adapters/index.js +13 -3
- package/dist/adapters/index.js.map +1 -1
- package/dist/auth/index.cjs.map +1 -1
- package/dist/auth/index.js.map +1 -1
- package/dist/core/index.cjs +13 -3
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +3 -3
- package/dist/core/index.d.ts +3 -3
- package/dist/core/index.js +13 -3
- package/dist/core/index.js.map +1 -1
- package/dist/index.cjs +88 -19
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +74 -15
- package/dist/index.d.ts +74 -15
- package/dist/index.js +79 -19
- package/dist/index.js.map +1 -1
- package/dist/repository/index.cjs +50 -11
- package/dist/repository/index.cjs.map +1 -1
- package/dist/repository/index.d.cts +3 -3
- package/dist/repository/index.d.ts +3 -3
- package/dist/repository/index.js +50 -11
- package/dist/repository/index.js.map +1 -1
- package/dist/sdk/index.cjs.map +1 -1
- package/dist/sdk/index.d.cts +2 -2
- package/dist/sdk/index.d.ts +2 -2
- package/dist/sdk/index.js.map +1 -1
- package/dist/service/index.cjs +60 -12
- package/dist/service/index.cjs.map +1 -1
- package/dist/service/index.d.cts +18 -3
- package/dist/service/index.d.ts +18 -3
- package/dist/service/index.js +60 -12
- package/dist/service/index.js.map +1 -1
- package/dist/{types-BBxIWHK2.d.cts → types-DgQtamEe.d.cts} +16 -3
- package/dist/{types-BBxIWHK2.d.ts → types-DgQtamEe.d.ts} +16 -3
- package/dist/{types-D1vdeBcA.d.cts → types-KgNdBuEF.d.cts} +1 -1
- package/dist/{types-aJsyv8df.d.ts → types-fW2JNmmr.d.ts} +1 -1
- package/package.json +1 -1
package/dist/sdk/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/sdk/types.ts","../../src/sdk/errors.ts","../../src/sdk/client.ts","../../src/sdk/admin-client.ts"],"sourcesContent":["/**\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"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/sdk/types.ts","../../src/sdk/errors.ts","../../src/sdk/client.ts","../../src/sdk/admin-client.ts"],"sourcesContent":["/**\r\n * SDK types for the credits system\r\n *\r\n * These types are used by the client SDKs for REST API consumption.\r\n */\r\n\r\n// Re-export core types for SDK consumers\r\nexport type {\r\n PortableUserCredits as UserCredits,\r\n PortableReservation as CreditReservation,\r\n CreditCheckResult,\r\n SubscriptionTier,\r\n} from \"../core/types.js\";\r\n\r\n/**\r\n * Configuration for CreditsClient\r\n */\r\nexport interface CreditsClientConfig {\r\n /** Base URL of the credits API (e.g., \"https://api.example.com\") */\r\n baseUrl: string;\r\n /** Function to get the auth token for session-based auth */\r\n getAuthToken?: () => Promise<string>;\r\n /** Optional custom fetch function for testing */\r\n fetch?: typeof fetch;\r\n}\r\n\r\n/**\r\n * Configuration for AdminCreditsClient\r\n */\r\nexport interface AdminCreditsClientConfig {\r\n /** Base URL of the credits API */\r\n baseUrl: string;\r\n /** Admin API key */\r\n apiKey: string;\r\n /** Optional custom fetch function for testing */\r\n fetch?: typeof fetch;\r\n}\r\n\r\n/**\r\n * Result of a credit reservation\r\n */\r\nexport interface ReservationResult {\r\n /** The reservation ID */\r\n reservationId: string;\r\n /** Amount of credits reserved */\r\n amount: number;\r\n /** When the reservation expires (ISO 8601) */\r\n expiresAt: string;\r\n}\r\n\r\n/**\r\n * Pagination options for API requests\r\n */\r\nexport interface PaginationOptions {\r\n /** Page number (1-indexed) */\r\n page?: number;\r\n /** Number of items per page */\r\n limit?: number;\r\n}\r\n\r\n/**\r\n * Usage history entry\r\n */\r\nexport interface UsageHistoryEntry {\r\n /** Unique ID of the usage log */\r\n id: string;\r\n /** Type of operation */\r\n operationType: string;\r\n /** AI provider used */\r\n provider: string;\r\n /** Credits consumed */\r\n creditsUsed: number;\r\n /** Whether the operation was successful */\r\n success: boolean;\r\n /** Error message if unsuccessful */\r\n errorMessage?: string;\r\n /** ID of the related resource */\r\n resourceId?: string;\r\n /** Type of the related resource */\r\n resourceType?: string;\r\n /** When the operation occurred (ISO 8601) */\r\n createdAt: string;\r\n}\r\n\r\n/**\r\n * Response from usage history endpoint\r\n */\r\nexport interface UsageHistoryResponse {\r\n /** List of usage entries */\r\n entries: UsageHistoryEntry[];\r\n /** Whether there are more results */\r\n hasMore: boolean;\r\n /** Total count (if available) */\r\n total?: number;\r\n}\r\n\r\n/**\r\n * Generic API response wrapper\r\n */\r\nexport interface ApiResponse<T> {\r\n success: boolean;\r\n data?: T;\r\n error?: {\r\n code: string;\r\n message: string;\r\n details?: unknown;\r\n };\r\n}\r\n\r\n/**\r\n * SDK error codes\r\n */\r\nexport const SDKErrorCode = {\r\n NETWORK_ERROR: \"NETWORK_ERROR\",\r\n AUTHENTICATION_ERROR: \"AUTHENTICATION_ERROR\",\r\n AUTHORIZATION_ERROR: \"AUTHORIZATION_ERROR\",\r\n INSUFFICIENT_CREDITS: \"INSUFFICIENT_CREDITS\",\r\n RESERVATION_NOT_FOUND: \"RESERVATION_NOT_FOUND\",\r\n RESERVATION_EXPIRED: \"RESERVATION_EXPIRED\",\r\n VALIDATION_ERROR: \"VALIDATION_ERROR\",\r\n SERVER_ERROR: \"SERVER_ERROR\",\r\n UNKNOWN_ERROR: \"UNKNOWN_ERROR\",\r\n} as const;\r\n\r\nexport type SDKErrorCodeType = (typeof SDKErrorCode)[keyof typeof SDKErrorCode];\r\n","/**\r\n * SDK error classes\r\n *\r\n * Provides typed error handling for SDK operations.\r\n */\r\n\r\nimport { SDKErrorCode, type SDKErrorCodeType } from \"./types.js\";\r\n\r\n/**\r\n * Base error class for SDK errors\r\n */\r\nexport class CreditsSDKError extends Error {\r\n public readonly code: SDKErrorCodeType;\r\n public readonly details?: Record<string, unknown>;\r\n public readonly statusCode?: number;\r\n\r\n constructor(\r\n message: string,\r\n code: SDKErrorCodeType,\r\n options?: {\r\n details?: Record<string, unknown>;\r\n statusCode?: number;\r\n cause?: Error;\r\n }\r\n ) {\r\n super(message, { cause: options?.cause });\r\n this.name = \"CreditsSDKError\";\r\n this.code = code;\r\n this.details = options?.details;\r\n this.statusCode = options?.statusCode;\r\n }\r\n\r\n /**\r\n * Check if this is a specific error type\r\n */\r\n is(code: SDKErrorCodeType): boolean {\r\n return this.code === code;\r\n }\r\n\r\n /**\r\n * Create a JSON representation for logging\r\n */\r\n toJSON(): Record<string, unknown> {\r\n return {\r\n name: this.name,\r\n message: this.message,\r\n code: this.code,\r\n details: this.details,\r\n statusCode: this.statusCode,\r\n };\r\n }\r\n}\r\n\r\n/**\r\n * Error thrown when a network request fails\r\n */\r\nexport class NetworkError extends CreditsSDKError {\r\n constructor(message: string, cause?: Error) {\r\n super(message, SDKErrorCode.NETWORK_ERROR, { cause });\r\n this.name = \"NetworkError\";\r\n }\r\n}\r\n\r\n/**\r\n * Error thrown when authentication fails\r\n */\r\nexport class AuthenticationError extends CreditsSDKError {\r\n constructor(message: string = \"Authentication required\") {\r\n super(message, SDKErrorCode.AUTHENTICATION_ERROR, { statusCode: 401 });\r\n this.name = \"AuthenticationError\";\r\n }\r\n}\r\n\r\n/**\r\n * Error thrown when authorization fails\r\n */\r\nexport class AuthorizationError extends CreditsSDKError {\r\n constructor(message: string = \"Access denied\") {\r\n super(message, SDKErrorCode.AUTHORIZATION_ERROR, { statusCode: 403 });\r\n this.name = \"AuthorizationError\";\r\n }\r\n}\r\n\r\n/**\r\n * Error thrown when credits are insufficient\r\n */\r\nexport class InsufficientCreditsError extends CreditsSDKError {\r\n public readonly available: number;\r\n public readonly required: number;\r\n\r\n constructor(available: number, required: number) {\r\n super(\r\n `Insufficient credits: available ${available}, required ${required}`,\r\n SDKErrorCode.INSUFFICIENT_CREDITS,\r\n {\r\n statusCode: 402,\r\n details: { available, required },\r\n }\r\n );\r\n this.name = \"InsufficientCreditsError\";\r\n this.available = available;\r\n this.required = required;\r\n }\r\n}\r\n\r\n/**\r\n * Error thrown when a reservation is not found\r\n */\r\nexport class ReservationNotFoundError extends CreditsSDKError {\r\n public readonly reservationId: string;\r\n\r\n constructor(reservationId: string) {\r\n super(\r\n `Reservation not found: ${reservationId}`,\r\n SDKErrorCode.RESERVATION_NOT_FOUND,\r\n {\r\n statusCode: 404,\r\n details: { reservationId },\r\n }\r\n );\r\n this.name = \"ReservationNotFoundError\";\r\n this.reservationId = reservationId;\r\n }\r\n}\r\n\r\n/**\r\n * Error thrown when a reservation has expired\r\n */\r\nexport class ReservationExpiredError extends CreditsSDKError {\r\n public readonly reservationId: string;\r\n\r\n constructor(reservationId: string) {\r\n super(\r\n `Reservation expired: ${reservationId}`,\r\n SDKErrorCode.RESERVATION_EXPIRED,\r\n {\r\n statusCode: 410,\r\n details: { reservationId },\r\n }\r\n );\r\n this.name = \"ReservationExpiredError\";\r\n this.reservationId = reservationId;\r\n }\r\n}\r\n\r\n/**\r\n * Error thrown for validation failures\r\n */\r\nexport class ValidationError extends CreditsSDKError {\r\n public readonly field?: string;\r\n\r\n constructor(message: string, field?: string) {\r\n super(message, SDKErrorCode.VALIDATION_ERROR, {\r\n statusCode: 400,\r\n details: field ? { field } : undefined,\r\n });\r\n this.name = \"ValidationError\";\r\n this.field = field;\r\n }\r\n}\r\n\r\n/**\r\n * Error thrown for server errors\r\n */\r\nexport class ServerError extends CreditsSDKError {\r\n constructor(message: string = \"Internal server error\") {\r\n super(message, SDKErrorCode.SERVER_ERROR, { statusCode: 500 });\r\n this.name = \"ServerError\";\r\n }\r\n}\r\n\r\n/**\r\n * Parse an API error response into the appropriate SDK error\r\n */\r\nexport function parseApiError(\r\n statusCode: number,\r\n body: { error?: { code?: string; message?: string; details?: unknown } }\r\n): CreditsSDKError {\r\n const errorCode = body.error?.code || \"\";\r\n const errorMessage = body.error?.message || \"An error occurred\";\r\n const details = body.error?.details as Record<string, unknown> | undefined;\r\n\r\n switch (statusCode) {\r\n case 401:\r\n return new AuthenticationError(errorMessage);\r\n case 403:\r\n return new AuthorizationError(errorMessage);\r\n case 402:\r\n // Parse insufficient credits details\r\n if (details?.available !== undefined && details?.required !== undefined) {\r\n return new InsufficientCreditsError(\r\n details.available as number,\r\n details.required as number\r\n );\r\n }\r\n return new CreditsSDKError(errorMessage, SDKErrorCode.INSUFFICIENT_CREDITS, {\r\n statusCode,\r\n details,\r\n });\r\n case 404:\r\n if (errorCode === \"RESERVATION_NOT_FOUND\" && details?.reservationId) {\r\n return new ReservationNotFoundError(details.reservationId as string);\r\n }\r\n return new CreditsSDKError(errorMessage, SDKErrorCode.RESERVATION_NOT_FOUND, {\r\n statusCode,\r\n details,\r\n });\r\n case 410:\r\n if (errorCode === \"RESERVATION_EXPIRED\" && details?.reservationId) {\r\n return new ReservationExpiredError(details.reservationId as string);\r\n }\r\n return new CreditsSDKError(errorMessage, SDKErrorCode.RESERVATION_EXPIRED, {\r\n statusCode,\r\n details,\r\n });\r\n case 400:\r\n case 422:\r\n return new ValidationError(errorMessage, details?.field as string | undefined);\r\n case 500:\r\n default:\r\n if (statusCode >= 500) {\r\n return new ServerError(errorMessage);\r\n }\r\n return new CreditsSDKError(errorMessage, SDKErrorCode.UNKNOWN_ERROR, {\r\n statusCode,\r\n details,\r\n });\r\n }\r\n}\r\n","/**\r\n * CreditsClient - REST API client for the credits system\r\n *\r\n * Provides a type-safe interface for consuming credits API endpoints\r\n * from external services or other applications.\r\n *\r\n * @example\r\n * ```typescript\r\n * const client = new CreditsClient({\r\n * baseUrl: process.env.CREDITS_API_URL,\r\n * getAuthToken: async () => session.accessToken,\r\n * });\r\n *\r\n * // Check if user has sufficient credits\r\n * const check = await client.checkAvailability(\"story_generation\");\r\n * if (!check.hasCredits) {\r\n * console.log(\"Insufficient credits\");\r\n * return;\r\n * }\r\n *\r\n * // Two-phase commit pattern\r\n * const reservation = await client.reserve(\"story_generation\");\r\n * try {\r\n * const result = await doExpensiveWork();\r\n * await client.commit(reservation.reservationId);\r\n * return result;\r\n * } catch (error) {\r\n * await client.release(reservation.reservationId);\r\n * throw error;\r\n * }\r\n * ```\r\n */\r\n\r\nimport type {\r\n CreditsClientConfig,\r\n UserCredits,\r\n CreditCheckResult,\r\n ReservationResult,\r\n UsageHistoryResponse,\r\n PaginationOptions,\r\n} from \"./types.js\";\r\nimport { parseApiError, NetworkError } from \"./errors.js\";\r\n\r\nexport class CreditsClient {\r\n private readonly baseUrl: string;\r\n private readonly getAuthToken?: () => Promise<string>;\r\n private readonly fetchFn: typeof fetch;\r\n\r\n constructor(config: CreditsClientConfig) {\r\n // Ensure baseUrl doesn't have trailing slash\r\n this.baseUrl = config.baseUrl.replace(/\\/$/, \"\");\r\n this.getAuthToken = config.getAuthToken;\r\n this.fetchFn = config.fetch ?? fetch;\r\n }\r\n\r\n /**\r\n * Make an authenticated request to the API\r\n */\r\n private async request<T>(\r\n path: string,\r\n options: {\r\n method?: \"GET\" | \"POST\" | \"PUT\" | \"DELETE\";\r\n body?: unknown;\r\n params?: Record<string, string | number | undefined>;\r\n } = {}\r\n ): Promise<T> {\r\n const { method = \"GET\", body, params } = options;\r\n\r\n // Build URL with query params\r\n let url = `${this.baseUrl}${path}`;\r\n if (params) {\r\n const searchParams = new URLSearchParams();\r\n for (const [key, value] of Object.entries(params)) {\r\n if (value !== undefined) {\r\n searchParams.set(key, String(value));\r\n }\r\n }\r\n const queryString = searchParams.toString();\r\n if (queryString) {\r\n url += `?${queryString}`;\r\n }\r\n }\r\n\r\n // Build headers\r\n const headers: Record<string, string> = {\r\n \"Content-Type\": \"application/json\",\r\n };\r\n\r\n // Add auth token if available\r\n if (this.getAuthToken) {\r\n try {\r\n const token = await this.getAuthToken();\r\n if (token) {\r\n headers[\"Authorization\"] = `Bearer ${token}`;\r\n }\r\n } catch {\r\n // Auth token not available, proceed without it\r\n }\r\n }\r\n\r\n // Make the request\r\n let response: Response;\r\n try {\r\n response = await this.fetchFn(url, {\r\n method,\r\n headers,\r\n body: body ? JSON.stringify(body) : undefined,\r\n });\r\n } catch (error) {\r\n throw new NetworkError(\r\n error instanceof Error ? error.message : \"Network request failed\",\r\n error instanceof Error ? error : undefined\r\n );\r\n }\r\n\r\n // Parse response\r\n const responseBody = (await response.json()) as {\r\n data?: T;\r\n error?: { code?: string; message?: string; details?: unknown };\r\n };\r\n\r\n // Handle errors\r\n if (!response.ok) {\r\n throw parseApiError(response.status, responseBody);\r\n }\r\n\r\n // Return data\r\n return responseBody.data as T;\r\n }\r\n\r\n /**\r\n * Get the current user's credit balance\r\n *\r\n * @returns User's credit information\r\n * @throws AuthenticationError if not authenticated\r\n */\r\n async getBalance(): Promise<UserCredits> {\r\n return this.request<UserCredits>(\"/api/v1/credits/balance\");\r\n }\r\n\r\n /**\r\n * Check if the user has sufficient credits for an operation\r\n *\r\n * @param operationType - Type of operation to check\r\n * @param customCost - Optional custom cost to check against\r\n * @returns Credit availability status\r\n * @throws AuthenticationError if not authenticated\r\n */\r\n async checkAvailability(\r\n operationType: string,\r\n customCost?: number\r\n ): Promise<CreditCheckResult> {\r\n return this.request<CreditCheckResult>(\"/api/v1/credits/availability\", {\r\n params: {\r\n operationType,\r\n customCost: customCost?.toString(),\r\n },\r\n });\r\n }\r\n\r\n /**\r\n * Reserve credits for an operation\r\n *\r\n * This is the first step in the two-phase commit pattern.\r\n * Reserved credits are held for 5 minutes before expiring.\r\n *\r\n * @param operationType - Type of operation\r\n * @param options - Optional reservation options\r\n * @returns Reservation details including ID and expiry\r\n * @throws InsufficientCreditsError if not enough credits\r\n * @throws AuthenticationError if not authenticated\r\n */\r\n async reserve(\r\n operationType: string,\r\n options?: {\r\n customCost?: number;\r\n resourceId?: string;\r\n resourceType?: string;\r\n }\r\n ): Promise<ReservationResult> {\r\n return this.request<ReservationResult>(\"/api/v1/credits/reserve\", {\r\n method: \"POST\",\r\n body: {\r\n operationType,\r\n ...options,\r\n },\r\n });\r\n }\r\n\r\n /**\r\n * Commit a reservation (deduct credits)\r\n *\r\n * This is the second step in the two-phase commit pattern.\r\n * Call this after your operation succeeds.\r\n *\r\n * @param reservationId - ID of the reservation to commit\r\n * @throws ReservationNotFoundError if reservation doesn't exist\r\n * @throws ReservationExpiredError if reservation has expired\r\n * @throws AuthenticationError if not authenticated\r\n */\r\n async commit(reservationId: string): Promise<void> {\r\n await this.request<{ message: string }>(\"/api/v1/credits/commit\", {\r\n method: \"POST\",\r\n body: { reservationId },\r\n });\r\n }\r\n\r\n /**\r\n * Release a reservation (refund credits)\r\n *\r\n * Call this if your operation fails after reserving credits.\r\n *\r\n * @param reservationId - ID of the reservation to release\r\n * @throws ReservationNotFoundError if reservation doesn't exist\r\n * @throws AuthenticationError if not authenticated\r\n */\r\n async release(reservationId: string): Promise<void> {\r\n await this.request<{ message: string }>(\"/api/v1/credits/release\", {\r\n method: \"POST\",\r\n body: { reservationId },\r\n });\r\n }\r\n\r\n /**\r\n * Get usage history for the current user\r\n *\r\n * @param options - Optional pagination options\r\n * @returns Paginated list of usage entries\r\n * @throws AuthenticationError if not authenticated\r\n */\r\n async getHistory(options?: PaginationOptions): Promise<UsageHistoryResponse> {\r\n return this.request<UsageHistoryResponse>(\"/api/v1/credits/history\", {\r\n params: {\r\n page: options?.page,\r\n limit: options?.limit,\r\n },\r\n });\r\n }\r\n\r\n /**\r\n * Get the available credits (balance + bonus - reserved)\r\n *\r\n * Convenience method that calculates available credits from balance.\r\n *\r\n * @returns Number of available credits\r\n * @throws AuthenticationError if not authenticated\r\n */\r\n async getAvailableCredits(): Promise<number> {\r\n const balance = await this.getBalance();\r\n return balance.balance + balance.bonusCredits - balance.reserved;\r\n }\r\n\r\n /**\r\n * Execute an operation with automatic credit handling\r\n *\r\n * This is a convenience method that handles the full two-phase commit\r\n * pattern automatically.\r\n *\r\n * @param operationType - Type of operation\r\n * @param operation - The operation to execute\r\n * @param options - Optional reservation options\r\n * @returns Result of the operation\r\n * @throws InsufficientCreditsError if not enough credits\r\n * @throws AuthenticationError if not authenticated\r\n *\r\n * @example\r\n * ```typescript\r\n * const result = await client.withCredits(\r\n * \"story_generation\",\r\n * async () => {\r\n * return generateStory(data);\r\n * }\r\n * );\r\n * ```\r\n */\r\n async withCredits<T>(\r\n operationType: string,\r\n operation: () => Promise<T>,\r\n options?: {\r\n customCost?: number;\r\n resourceId?: string;\r\n resourceType?: string;\r\n }\r\n ): Promise<T> {\r\n const reservation = await this.reserve(operationType, options);\r\n\r\n try {\r\n const result = await operation();\r\n await this.commit(reservation.reservationId);\r\n return result;\r\n } catch (error) {\r\n // Always attempt to release on failure\r\n try {\r\n await this.release(reservation.reservationId);\r\n } catch {\r\n // Ignore release errors, original error is more important\r\n }\r\n throw error;\r\n }\r\n }\r\n}\r\n","/**\r\n * AdminCreditsClient - Admin REST API client for the credits system\r\n *\r\n * Provides administrative access to the credits system for managing\r\n * user credits, configurations, and subscriptions.\r\n *\r\n * @example\r\n * ```typescript\r\n * const adminClient = new AdminCreditsClient({\r\n * baseUrl: process.env.CREDITS_API_URL,\r\n * apiKey: process.env.CREDITS_ADMIN_API_KEY,\r\n * });\r\n *\r\n * // Add credits to a user\r\n * await adminClient.addCredits(\"user-123\", 100, \"Support credit grant\");\r\n *\r\n * // Update subscription tier\r\n * await adminClient.updateSubscription(\"user-123\", \"premium\", \"2027-01-01T00:00:00Z\");\r\n *\r\n * // Update operation costs\r\n * await adminClient.updateCosts({\r\n * story_generation: 8,\r\n * image_generation: 12,\r\n * });\r\n * ```\r\n */\r\n\r\nimport type { AdminCreditsClientConfig, UserCredits } from \"./types.js\";\r\nimport { parseApiError, NetworkError } from \"./errors.js\";\r\n\r\n/**\r\n * Credits configuration returned by the API\r\n */\r\nexport interface CreditsConfig {\r\n operationCosts: Record<string, number>;\r\n tierConfigs: Record<\r\n string,\r\n {\r\n monthlyLimit: number;\r\n features: string[];\r\n }\r\n >;\r\n}\r\n\r\n/**\r\n * Response from listing users\r\n */\r\nexport interface ListUsersResponse {\r\n users: Array<{\r\n userId: string;\r\n balance: number;\r\n bonusCredits?: number;\r\n tier: string;\r\n monthlyUsed?: number;\r\n }>;\r\n hasMore: boolean;\r\n total?: number;\r\n}\r\n\r\n/**\r\n * Options for listing users\r\n */\r\nexport interface ListUsersOptions {\r\n page?: number;\r\n limit?: number;\r\n tier?: string;\r\n search?: string;\r\n}\r\n\r\nexport class AdminCreditsClient {\r\n private readonly baseUrl: string;\r\n private readonly apiKey: string;\r\n private readonly fetchFn: typeof fetch;\r\n\r\n constructor(config: AdminCreditsClientConfig) {\r\n // Ensure baseUrl doesn't have trailing slash\r\n this.baseUrl = config.baseUrl.replace(/\\/$/, \"\");\r\n this.apiKey = config.apiKey;\r\n this.fetchFn = config.fetch ?? fetch;\r\n }\r\n\r\n /**\r\n * Make an authenticated admin request to the API\r\n */\r\n private async request<T>(\r\n path: string,\r\n options: {\r\n method?: \"GET\" | \"POST\" | \"PUT\" | \"DELETE\";\r\n body?: unknown;\r\n params?: Record<string, string | number | undefined>;\r\n } = {}\r\n ): Promise<T> {\r\n const { method = \"GET\", body, params } = options;\r\n\r\n // Build URL with query params\r\n let url = `${this.baseUrl}${path}`;\r\n if (params) {\r\n const searchParams = new URLSearchParams();\r\n for (const [key, value] of Object.entries(params)) {\r\n if (value !== undefined) {\r\n searchParams.set(key, String(value));\r\n }\r\n }\r\n const queryString = searchParams.toString();\r\n if (queryString) {\r\n url += `?${queryString}`;\r\n }\r\n }\r\n\r\n // Build headers with admin API key\r\n const headers: Record<string, string> = {\r\n \"Content-Type\": \"application/json\",\r\n \"x-api-key\": this.apiKey,\r\n };\r\n\r\n // Make the request\r\n let response: Response;\r\n try {\r\n response = await this.fetchFn(url, {\r\n method,\r\n headers,\r\n body: body ? JSON.stringify(body) : undefined,\r\n });\r\n } catch (error) {\r\n throw new NetworkError(\r\n error instanceof Error ? error.message : \"Network request failed\",\r\n error instanceof Error ? error : undefined\r\n );\r\n }\r\n\r\n // Parse response\r\n const responseBody = (await response.json()) as {\r\n data?: T;\r\n error?: { code?: string; message?: string; details?: unknown };\r\n };\r\n\r\n // Handle errors\r\n if (!response.ok) {\r\n throw parseApiError(response.status, responseBody);\r\n }\r\n\r\n // Return data\r\n return responseBody.data as T;\r\n }\r\n\r\n /**\r\n * Get a specific user's credits\r\n *\r\n * @param userId - ID of the user\r\n * @returns User's credit information\r\n * @throws AuthorizationError if API key is invalid\r\n */\r\n async getUserCredits(userId: string): Promise<UserCredits> {\r\n return this.request<UserCredits>(`/api/admin/credits/users/${userId}`);\r\n }\r\n\r\n /**\r\n * Add credits to a user's account\r\n *\r\n * Credits are added to bonusCredits, which don't expire on monthly reset.\r\n *\r\n * @param userId - ID of the user\r\n * @param amount - Amount of credits to add\r\n * @param description - Reason for adding credits\r\n * @throws AuthorizationError if API key is invalid\r\n */\r\n async addCredits(\r\n userId: string,\r\n amount: number,\r\n description: string\r\n ): Promise<void> {\r\n await this.request<{ message: string; newBalance: number }>(\r\n `/api/admin/credits/users/${userId}/add`,\r\n {\r\n method: \"POST\",\r\n body: { amount, description },\r\n }\r\n );\r\n }\r\n\r\n /**\r\n * Update a user's subscription tier\r\n *\r\n * @param userId - ID of the user\r\n * @param tier - New subscription tier\r\n * @param expiresAt - Optional expiration date (ISO 8601)\r\n * @throws AuthorizationError if API key is invalid\r\n */\r\n async updateSubscription(\r\n userId: string,\r\n tier: string,\r\n expiresAt?: string\r\n ): Promise<void> {\r\n const body: { tier: string; expiresAt?: string } = { tier };\r\n if (expiresAt) {\r\n body.expiresAt = expiresAt;\r\n }\r\n\r\n await this.request<{ message: string }>(\r\n `/api/admin/credits/users/${userId}/subscription`,\r\n {\r\n method: \"PUT\",\r\n body,\r\n }\r\n );\r\n }\r\n\r\n /**\r\n * Get the current credits configuration\r\n *\r\n * @returns Credits system configuration\r\n * @throws AuthorizationError if API key is invalid\r\n */\r\n async getConfig(): Promise<CreditsConfig> {\r\n return this.request<CreditsConfig>(\"/api/admin/credits/config\");\r\n }\r\n\r\n /**\r\n * Update operation costs\r\n *\r\n * @param costs - New operation costs (operation type -> cost)\r\n * @throws AuthorizationError if API key is invalid\r\n */\r\n async updateCosts(costs: Record<string, number>): Promise<void> {\r\n await this.request<{ message: string }>(\"/api/admin/credits/config/costs\", {\r\n method: \"PUT\",\r\n body: { operationCosts: costs },\r\n });\r\n }\r\n\r\n /**\r\n * List all users with their credits\r\n *\r\n * @param options - Pagination and filtering options\r\n * @returns Paginated list of users\r\n * @throws AuthorizationError if API key is invalid\r\n */\r\n async listUsers(options?: ListUsersOptions): Promise<ListUsersResponse> {\r\n return this.request<ListUsersResponse>(\"/api/admin/credits/users\", {\r\n params: {\r\n page: options?.page,\r\n limit: options?.limit,\r\n tier: options?.tier,\r\n search: options?.search,\r\n },\r\n });\r\n }\r\n\r\n /**\r\n * Deduct credits from a user's account\r\n *\r\n * @param userId - ID of the user\r\n * @param amount - Amount of credits to deduct\r\n * @param description - Reason for deducting credits\r\n * @throws AuthorizationError if API key is invalid\r\n */\r\n async deductCredits(\r\n userId: string,\r\n amount: number,\r\n description: string\r\n ): Promise<void> {\r\n await this.request<{ message: string; newBalance: number }>(\r\n `/api/admin/credits/users/${userId}/deduct`,\r\n {\r\n method: \"POST\",\r\n body: { amount, description },\r\n }\r\n );\r\n }\r\n\r\n /**\r\n * Get usage statistics for a user\r\n *\r\n * @param userId - ID of the user\r\n * @param options - Optional date range\r\n * @returns Usage statistics\r\n * @throws AuthorizationError if API key is invalid\r\n */\r\n async getUserStats(\r\n userId: string,\r\n options?: { startDate?: string; endDate?: string }\r\n ): Promise<{\r\n totalCreditsUsed: number;\r\n operationBreakdown: Record<string, number>;\r\n successRate: number;\r\n }> {\r\n return this.request<{\r\n totalCreditsUsed: number;\r\n operationBreakdown: Record<string, number>;\r\n successRate: number;\r\n }>(`/api/admin/credits/users/${userId}/stats`, {\r\n params: {\r\n startDate: options?.startDate,\r\n endDate: options?.endDate,\r\n },\r\n });\r\n }\r\n}\r\n"],"mappings":"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","names":[]}
|
package/dist/service/index.cjs
CHANGED
|
@@ -76,10 +76,13 @@ function getConfigProvider() {
|
|
|
76
76
|
|
|
77
77
|
// src/config/index.ts
|
|
78
78
|
var tierConfigSchema = import_zod.z.object({
|
|
79
|
-
tier: import_zod.z.
|
|
79
|
+
tier: import_zod.z.string().min(1),
|
|
80
80
|
monthlyCredits: import_zod.z.number().min(0),
|
|
81
81
|
priceUsd: import_zod.z.number().min(0),
|
|
82
|
-
features: import_zod.z.array(import_zod.z.string())
|
|
82
|
+
features: import_zod.z.array(import_zod.z.string()),
|
|
83
|
+
isFree: import_zod.z.boolean().optional(),
|
|
84
|
+
unlimited: import_zod.z.boolean().optional(),
|
|
85
|
+
isDefault: import_zod.z.boolean().optional()
|
|
83
86
|
});
|
|
84
87
|
var creditSystemConfigSchema = import_zod.z.object({
|
|
85
88
|
/**
|
|
@@ -103,7 +106,7 @@ var creditSystemConfigSchema = import_zod.z.object({
|
|
|
103
106
|
* Tier configurations
|
|
104
107
|
*/
|
|
105
108
|
tierConfigs: import_zod.z.record(
|
|
106
|
-
import_zod.z.
|
|
109
|
+
import_zod.z.string().min(1),
|
|
107
110
|
tierConfigSchema
|
|
108
111
|
),
|
|
109
112
|
/**
|
|
@@ -159,6 +162,8 @@ var DEFAULT_CONFIG = {
|
|
|
159
162
|
tier: "free",
|
|
160
163
|
monthlyCredits: 25,
|
|
161
164
|
priceUsd: 0,
|
|
165
|
+
isFree: true,
|
|
166
|
+
isDefault: true,
|
|
162
167
|
features: [
|
|
163
168
|
"25 credits per month",
|
|
164
169
|
"Basic story generation",
|
|
@@ -192,6 +197,7 @@ var DEFAULT_CONFIG = {
|
|
|
192
197
|
tier: "unlimited",
|
|
193
198
|
monthlyCredits: 0,
|
|
194
199
|
// 0 = unlimited
|
|
200
|
+
unlimited: true,
|
|
195
201
|
priceUsd: 49.99,
|
|
196
202
|
features: [
|
|
197
203
|
"Unlimited credits",
|
|
@@ -304,10 +310,44 @@ function getConfig() {
|
|
|
304
310
|
}
|
|
305
311
|
return currentConfig;
|
|
306
312
|
}
|
|
313
|
+
function getValidTiers() {
|
|
314
|
+
return Object.keys(getConfig().tierConfigs);
|
|
315
|
+
}
|
|
316
|
+
function isValidTier(tier) {
|
|
317
|
+
return tier in getConfig().tierConfigs;
|
|
318
|
+
}
|
|
319
|
+
function isFreeTier(tier) {
|
|
320
|
+
const c = getConfig().tierConfigs[tier];
|
|
321
|
+
if (!c) return false;
|
|
322
|
+
return c.isFree ?? c.priceUsd === 0;
|
|
323
|
+
}
|
|
324
|
+
function isUnlimitedTier(tier) {
|
|
325
|
+
const c = getConfig().tierConfigs[tier];
|
|
326
|
+
if (!c) return false;
|
|
327
|
+
return c.unlimited ?? c.monthlyCredits === 0;
|
|
328
|
+
}
|
|
329
|
+
function getDefaultTier() {
|
|
330
|
+
const entries = Object.entries(getConfig().tierConfigs);
|
|
331
|
+
const def = entries.find(([, c]) => c.isDefault)?.[0] ?? entries.find(([, c]) => c.isFree ?? c.priceUsd === 0)?.[0];
|
|
332
|
+
return def ?? "free";
|
|
333
|
+
}
|
|
334
|
+
var UNLIMITED_BALANCE_SENTINEL = 999999;
|
|
335
|
+
function getUnlimitedSentinelBalance() {
|
|
336
|
+
return UNLIMITED_BALANCE_SENTINEL;
|
|
337
|
+
}
|
|
338
|
+
function getConfigTierConfig(tier) {
|
|
339
|
+
const cfg = getConfig().tierConfigs[tier];
|
|
340
|
+
if (cfg) return cfg;
|
|
341
|
+
const fallback = getDefaultTier();
|
|
342
|
+
console.warn(
|
|
343
|
+
`[Credits Config] Unknown tier "${tier}". Falling back to "${fallback}". Valid: ${getValidTiers().join(", ")}`
|
|
344
|
+
);
|
|
345
|
+
return getConfig().tierConfigs[fallback];
|
|
346
|
+
}
|
|
307
347
|
function getConfigMonthlyLimit(tier) {
|
|
308
|
-
|
|
309
|
-
return config.monthlyCredits === 0 ? Infinity : config.monthlyCredits;
|
|
348
|
+
return isUnlimitedTier(tier) ? Infinity : getConfigTierConfig(tier).monthlyCredits;
|
|
310
349
|
}
|
|
350
|
+
var tierSchema = import_zod.z.string().refine(isValidTier, { message: "Unknown subscription tier" });
|
|
311
351
|
function snakeCaseToTitleCase(str) {
|
|
312
352
|
return str.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
313
353
|
}
|
|
@@ -359,7 +399,7 @@ var CreditsService = class {
|
|
|
359
399
|
if (!data) {
|
|
360
400
|
return null;
|
|
361
401
|
}
|
|
362
|
-
if (data.tier
|
|
402
|
+
if (!isFreeTier(data.tier) && data.subscriptionExpiresAt) {
|
|
363
403
|
const expiryResult = await this.repository.checkAndHandleSubscriptionExpiry(
|
|
364
404
|
userId,
|
|
365
405
|
getConfig().subscriptionGracePeriodDays
|
|
@@ -428,7 +468,7 @@ var CreditsService = class {
|
|
|
428
468
|
async initializeUserCredits(userId) {
|
|
429
469
|
const credits = await this.repository.initializeUserCredits(
|
|
430
470
|
userId,
|
|
431
|
-
|
|
471
|
+
getDefaultTier(),
|
|
432
472
|
getConfig().defaultFreeCredits
|
|
433
473
|
);
|
|
434
474
|
return toClientUserCredits(credits);
|
|
@@ -470,11 +510,13 @@ var CreditsService = class {
|
|
|
470
510
|
* @param userId - User ID
|
|
471
511
|
* @param amount - Credits to reserve
|
|
472
512
|
* @param operationType - Operation type for tracking
|
|
513
|
+
* @param options - Optional settings (e.g. a custom `ttlMs` for long-running async jobs)
|
|
473
514
|
* @returns Reservation object
|
|
474
515
|
* @throws Error if insufficient credits
|
|
475
516
|
*/
|
|
476
|
-
async reserveCredits(userId, amount, operationType) {
|
|
477
|
-
const
|
|
517
|
+
async reserveCredits(userId, amount, operationType, options) {
|
|
518
|
+
const ttlMs = options?.ttlMs ?? getConfig().reservationExpiryMs;
|
|
519
|
+
const expiresAt = new Date(Date.now() + ttlMs);
|
|
478
520
|
return this.repository.reserveCreditsAtomic(userId, amount, operationType, expiresAt);
|
|
479
521
|
}
|
|
480
522
|
/**
|
|
@@ -487,6 +529,9 @@ var CreditsService = class {
|
|
|
487
529
|
if (!reservation) {
|
|
488
530
|
throw new Error(`Reservation ${reservationId} not found`);
|
|
489
531
|
}
|
|
532
|
+
if (reservation.status === "committed") {
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
490
535
|
await this.repository.commitReservationAtomic(userId, reservationId);
|
|
491
536
|
const credits = await this.repository.getUserCredits(userId);
|
|
492
537
|
if (credits) {
|
|
@@ -563,12 +608,15 @@ var CreditsService = class {
|
|
|
563
608
|
*/
|
|
564
609
|
async updateTier(userId, tier, expiresAt) {
|
|
565
610
|
const monthlyLimit = getConfigMonthlyLimit(tier);
|
|
611
|
+
const unlimited = isUnlimitedTier(tier);
|
|
612
|
+
const free = isFreeTier(tier);
|
|
566
613
|
await this.repository.updateUserTier(userId, {
|
|
567
614
|
tier,
|
|
568
|
-
|
|
615
|
+
// Stored monthlyLimit uses the 0-means-unlimited convention.
|
|
616
|
+
monthlyLimit: unlimited ? 0 : monthlyLimit,
|
|
569
617
|
// Reset balance to new tier limit if upgrading
|
|
570
|
-
balance:
|
|
571
|
-
monthlyUsed:
|
|
618
|
+
balance: !free ? unlimited ? getUnlimitedSentinelBalance() : monthlyLimit : void 0,
|
|
619
|
+
monthlyUsed: !free ? 0 : void 0,
|
|
572
620
|
subscriptionExpiresAt: expiresAt ? expiresAt.toISOString() : null
|
|
573
621
|
});
|
|
574
622
|
}
|