@nehorai/credits 1.4.0 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/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/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 +155 -17
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +75 -16
- package/dist/index.d.ts +75 -16
- package/dist/index.js +146 -17
- package/dist/index.js.map +1 -1
- package/dist/repository/index.cjs +47 -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 +47 -11
- package/dist/repository/index.js.map +1 -1
- package/dist/sdk/index.d.cts +2 -2
- package/dist/sdk/index.d.ts +2 -2
- package/dist/service/index.cjs +130 -10
- package/dist/service/index.cjs.map +1 -1
- package/dist/service/index.d.cts +43 -3
- package/dist/service/index.d.ts +43 -3
- package/dist/service/index.js +130 -10
- package/dist/service/index.js.map +1 -1
- package/dist/{types-D1vdeBcA.d.cts → types-CZNv1U3e.d.cts} +1 -1
- package/dist/{types-BBxIWHK2.d.cts → types-CjLj3GW5.d.cts} +37 -3
- package/dist/{types-BBxIWHK2.d.ts → types-CjLj3GW5.d.ts} +37 -3
- package/dist/{types-aJsyv8df.d.ts → types-G4WK0rM-.d.ts} +1 -1
- package/package.json +10 -9
- package/LICENSE +0 -21
package/dist/adapters/index.cjs
CHANGED
|
@@ -35,10 +35,13 @@ function getConfigProvider() {
|
|
|
35
35
|
|
|
36
36
|
// src/config/index.ts
|
|
37
37
|
var tierConfigSchema = import_zod.z.object({
|
|
38
|
-
tier: import_zod.z.
|
|
38
|
+
tier: import_zod.z.string().min(1),
|
|
39
39
|
monthlyCredits: import_zod.z.number().min(0),
|
|
40
40
|
priceUsd: import_zod.z.number().min(0),
|
|
41
|
-
features: import_zod.z.array(import_zod.z.string())
|
|
41
|
+
features: import_zod.z.array(import_zod.z.string()),
|
|
42
|
+
isFree: import_zod.z.boolean().optional(),
|
|
43
|
+
unlimited: import_zod.z.boolean().optional(),
|
|
44
|
+
isDefault: import_zod.z.boolean().optional()
|
|
42
45
|
});
|
|
43
46
|
var creditSystemConfigSchema = import_zod.z.object({
|
|
44
47
|
/**
|
|
@@ -62,7 +65,7 @@ var creditSystemConfigSchema = import_zod.z.object({
|
|
|
62
65
|
* Tier configurations
|
|
63
66
|
*/
|
|
64
67
|
tierConfigs: import_zod.z.record(
|
|
65
|
-
import_zod.z.
|
|
68
|
+
import_zod.z.string().min(1),
|
|
66
69
|
tierConfigSchema
|
|
67
70
|
),
|
|
68
71
|
/**
|
|
@@ -118,6 +121,8 @@ var DEFAULT_CONFIG = {
|
|
|
118
121
|
tier: "free",
|
|
119
122
|
monthlyCredits: 25,
|
|
120
123
|
priceUsd: 0,
|
|
124
|
+
isFree: true,
|
|
125
|
+
isDefault: true,
|
|
121
126
|
features: [
|
|
122
127
|
"25 credits per month",
|
|
123
128
|
"Basic story generation",
|
|
@@ -151,6 +156,7 @@ var DEFAULT_CONFIG = {
|
|
|
151
156
|
tier: "unlimited",
|
|
152
157
|
monthlyCredits: 0,
|
|
153
158
|
// 0 = unlimited
|
|
159
|
+
unlimited: true,
|
|
154
160
|
priceUsd: 49.99,
|
|
155
161
|
features: [
|
|
156
162
|
"Unlimited credits",
|
|
@@ -263,6 +269,10 @@ function getConfig() {
|
|
|
263
269
|
}
|
|
264
270
|
return currentConfig;
|
|
265
271
|
}
|
|
272
|
+
function isValidTier(tier) {
|
|
273
|
+
return tier in getConfig().tierConfigs;
|
|
274
|
+
}
|
|
275
|
+
var tierSchema = import_zod.z.string().refine(isValidTier, { message: "Unknown subscription tier" });
|
|
266
276
|
function snakeCaseToTitleCase(str) {
|
|
267
277
|
return str.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
268
278
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"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 {\n ActionResult,\n CreditActionHandler,\n CreditsAdapterConfig,\n CreditsAdapter,\n} from \"./types.js\";\n\nexport { createGenericAdapter } from \"./generic.js\";\n","import { z } from \"zod\";\nimport type { SubscriptionTier, TierConfig } from \"../core/types.js\";\nimport { getConfigProvider } from \"./provider.js\";\n\n/**\n * Schema for tier configuration\n */\nconst tierConfigSchema = z.object({\n tier: z.enum([\"free\", \"basic\", \"premium\", \"unlimited\"]),\n monthlyCredits: z.number().min(0),\n priceUsd: z.number().min(0),\n features: z.array(z.string()),\n});\n\n/**\n * Schema for credit system configuration\n */\nconst creditSystemConfigSchema = z.object({\n /**\n * Operation costs in credits\n * Keys are operation type names (any non-empty string)\n * Values are positive numbers representing credit cost\n */\n operationCosts: z.record(\n z.string().min(1),\n z.number().positive()\n ),\n\n /**\n * Display labels for operation types (English canonical labels).\n * Falls back to auto-generating from snake_case keys if not provided.\n */\n operationLabels: z.record(\n z.string().min(1),\n z.string().min(1)\n ).optional().default({}),\n\n /**\n * Tier configurations\n */\n tierConfigs: z.record(\n z.enum([\"free\", \"basic\", \"premium\", \"unlimited\"]),\n tierConfigSchema\n ),\n\n /**\n * Reservation expiry time in milliseconds\n */\n reservationExpiryMs: z.number().positive().default(5 * 60 * 1000),\n\n /**\n * Default credits for new users (free tier)\n */\n defaultFreeCredits: z.number().positive().default(25),\n\n /**\n * Grace period in days after subscription expires before downgrade\n */\n subscriptionGracePeriodDays: z.number().min(0).default(3),\n\n /**\n * Low balance notification threshold\n */\n lowBalanceThreshold: z.number().min(0).default(10),\n\n /**\n * Cooldown in hours between low balance notifications\n */\n lowBalanceNotificationCooldownHours: z.number().positive().default(24),\n\n /**\n * Feature flags for optional features\n */\n features: z.object({\n journalEntries: z.boolean().default(true),\n notifications: z.boolean().default(true),\n subscriptionExpiry: z.boolean().default(true),\n usageHistory: z.boolean().default(true),\n }).default({\n journalEntries: true,\n notifications: true,\n subscriptionExpiry: true,\n usageHistory: true,\n }),\n});\n\n/**\n * Credit system configuration type\n */\nexport type CreditSystemConfig = z.infer<typeof creditSystemConfigSchema>;\n\n/**\n * Default configuration\n */\nconst DEFAULT_CONFIG: CreditSystemConfig = {\n operationCosts: {\n story_generation: 5,\n conversation: 2,\n image_generation: 10,\n template_generation: 10,\n },\n operationLabels: {\n story_generation: \"Story generation\",\n conversation: \"Conversation\",\n image_generation: \"Image generation\",\n template_generation: \"Template generation\",\n },\n tierConfigs: {\n free: {\n tier: \"free\",\n monthlyCredits: 25,\n priceUsd: 0,\n features: [\n \"25 credits per month\",\n \"Basic story generation\",\n \"Standard image quality\",\n ],\n },\n basic: {\n tier: \"basic\",\n monthlyCredits: 200,\n priceUsd: 9.99,\n features: [\n \"200 credits per month\",\n \"All story features\",\n \"High quality images\",\n \"Priority support\",\n ],\n },\n premium: {\n tier: \"premium\",\n monthlyCredits: 500,\n priceUsd: 19.99,\n features: [\n \"500 credits per month\",\n \"All features\",\n \"Highest quality images\",\n \"Early access to new features\",\n \"Priority support\",\n ],\n },\n unlimited: {\n tier: \"unlimited\",\n monthlyCredits: 0, // 0 = unlimited\n priceUsd: 49.99,\n features: [\n \"Unlimited credits\",\n \"All features\",\n \"Highest quality images\",\n \"Early access to new features\",\n \"Dedicated support\",\n ],\n },\n },\n reservationExpiryMs: 5 * 60 * 1000, // 5 minutes\n defaultFreeCredits: 25,\n subscriptionGracePeriodDays: 3,\n lowBalanceThreshold: 10,\n lowBalanceNotificationCooldownHours: 24,\n features: {\n journalEntries: true,\n notifications: true,\n subscriptionExpiry: true,\n usageHistory: true,\n },\n};\n\n/**\n * Current configuration (can be overridden)\n */\nlet currentConfig: CreditSystemConfig = DEFAULT_CONFIG;\n\n/**\n * Load configuration from environment variables\n * Can be extended to load from other sources (file, remote config, etc.)\n *\n * Operation costs are loaded from CREDITS_OPERATION_COSTS as JSON:\n * CREDITS_OPERATION_COSTS={\"story_generation\":5,\"image_generation\":10}\n */\nexport function loadConfigFromEnv(): Partial<CreditSystemConfig> {\n const envConfig: Partial<CreditSystemConfig> = {};\n\n // Load operation costs from env as JSON\n const operationCostsEnv = process.env.CREDITS_OPERATION_COSTS;\n if (operationCostsEnv) {\n try {\n const parsed = JSON.parse(operationCostsEnv);\n if (typeof parsed === \"object\" && parsed !== null) {\n envConfig.operationCosts = {\n ...DEFAULT_CONFIG.operationCosts,\n ...parsed,\n };\n }\n } catch {\n console.warn(\"[Credits Config] Failed to parse CREDITS_OPERATION_COSTS env var as JSON\");\n }\n }\n\n // Load other settings from env\n const reservationExpiry = process.env.CREDITS_RESERVATION_EXPIRY_MS;\n if (reservationExpiry) {\n envConfig.reservationExpiryMs = parseInt(reservationExpiry, 10);\n }\n\n const defaultCredits = process.env.CREDITS_DEFAULT_FREE;\n if (defaultCredits) {\n envConfig.defaultFreeCredits = parseInt(defaultCredits, 10);\n }\n\n const gracePeriod = process.env.CREDITS_GRACE_PERIOD_DAYS;\n if (gracePeriod) {\n envConfig.subscriptionGracePeriodDays = parseInt(gracePeriod, 10);\n }\n\n const lowBalanceThreshold = process.env.CREDITS_LOW_BALANCE_THRESHOLD;\n if (lowBalanceThreshold) {\n envConfig.lowBalanceThreshold = parseInt(lowBalanceThreshold, 10);\n }\n\n // Load feature flags\n const featuresEnv = process.env.CREDITS_FEATURES;\n if (featuresEnv) {\n try {\n const features = JSON.parse(featuresEnv);\n envConfig.features = {\n ...DEFAULT_CONFIG.features,\n ...features,\n };\n } catch {\n console.warn(\"[Credits Config] Failed to parse CREDITS_FEATURES env var\");\n }\n }\n\n return envConfig;\n}\n\n/**\n * Initialize configuration\n * Merges default config with environment overrides\n */\nexport function initializeConfig(overrides?: Partial<CreditSystemConfig>): CreditSystemConfig {\n const envConfig = loadConfigFromEnv();\n\n const mergedConfig = {\n ...DEFAULT_CONFIG,\n ...envConfig,\n ...overrides,\n operationCosts: {\n ...DEFAULT_CONFIG.operationCosts,\n ...envConfig.operationCosts,\n ...overrides?.operationCosts,\n },\n operationLabels: {\n ...DEFAULT_CONFIG.operationLabels,\n ...envConfig.operationLabels,\n ...overrides?.operationLabels,\n },\n tierConfigs: {\n ...DEFAULT_CONFIG.tierConfigs,\n ...envConfig.tierConfigs,\n ...overrides?.tierConfigs,\n },\n features: {\n ...DEFAULT_CONFIG.features,\n ...envConfig.features,\n ...overrides?.features,\n },\n };\n\n // Validate configuration\n const result = creditSystemConfigSchema.safeParse(mergedConfig);\n if (!result.success) {\n console.error(\"[Credits Config] Invalid configuration:\", result.error.issues);\n throw new Error(\"Invalid credit system configuration\");\n }\n\n currentConfig = result.data;\n return currentConfig;\n}\n\n/**\n * Get the current configuration.\n * If an external provider is registered, delegates to it.\n * Otherwise falls back to the local currentConfig.\n */\nexport function getConfig(): CreditSystemConfig {\n const provider = getConfigProvider();\n if (provider) {\n return provider.getConfig();\n }\n return currentConfig;\n}\n\n/**\n * Get all valid operation types from configuration\n * Returns the keys of the operationCosts object\n */\nexport function getValidOperationTypes(): string[] {\n return Object.keys(getConfig().operationCosts);\n}\n\n/**\n * Check if an operation type is valid (configured in the system)\n * @param type - The operation type to check\n * @returns true if the operation type is configured\n */\nexport function isValidOperationType(type: string): boolean {\n return type in getConfig().operationCosts;\n}\n\n/**\n * Get operation cost from configuration\n * @throws Error if the operation type is not configured\n */\nexport function getConfigOperationCost(operationType: string): number {\n const cost = getConfig().operationCosts[operationType];\n if (cost === undefined) {\n throw new Error(\n `Unknown operation type: ${operationType}. Valid types: ${getValidOperationTypes().join(\", \")}`\n );\n }\n return cost;\n}\n\n/**\n * Get tier configuration from configuration\n */\nexport function getConfigTierConfig(tier: SubscriptionTier): TierConfig {\n return getConfig().tierConfigs[tier]!;\n}\n\n/**\n * Get monthly limit for a tier from configuration\n * Returns Infinity for unlimited tier\n */\nexport function getConfigMonthlyLimit(tier: SubscriptionTier): number {\n const config = getConfig().tierConfigs[tier]!;\n return config.monthlyCredits === 0 ? Infinity : config.monthlyCredits;\n}\n\n/**\n * Check if a feature is enabled\n */\nexport function isFeatureEnabled(feature: keyof CreditSystemConfig[\"features\"]): boolean {\n return getConfig().features[feature];\n}\n\n/**\n * Convert a snake_case string to Title Case.\n * E.g., \"story_generation\" -> \"Story Generation\"\n */\nfunction snakeCaseToTitleCase(str: string): string {\n return str\n .replace(/_/g, \" \")\n .replace(/\\b\\w/g, (c) => c.toUpperCase());\n}\n\n/**\n * Get the display label for an operation type.\n * Falls back to converting snake_case to Title Case if no label is configured.\n */\nexport function getOperationLabel(operationType: string): string {\n const labels = getConfig().operationLabels;\n if (labels && operationType in labels) {\n return labels[operationType];\n }\n return snakeCaseToTitleCase(operationType);\n}\n\n/**\n * Get all operation labels as a record.\n * For any operation in operationCosts that lacks a label, generates one from the key.\n */\nexport function getOperationLabels(): Record<string, string> {\n const config = getConfig();\n const result: Record<string, string> = {};\n for (const key of Object.keys(config.operationCosts)) {\n result[key] = config.operationLabels?.[key]\n ?? snakeCaseToTitleCase(key);\n }\n return result;\n}\n\n/**\n * Reset configuration to defaults (for testing)\n */\nexport function resetConfig(): void {\n currentConfig = DEFAULT_CONFIG;\n}\n\n// Initialize configuration on module load\ninitializeConfig();\n","import type { CreditSystemConfig } from \"./index.js\";\n\n/** Interface for external config providers (e.g. Firestore-backed) */\nexport interface ICreditConfigProvider {\n getConfig(): CreditSystemConfig;\n}\n\nlet configProvider: ICreditConfigProvider | null = null;\n\n/** Register an external config provider. Called once at app startup. */\nexport function registerConfigProvider(provider: ICreditConfigProvider): void {\n configProvider = provider;\n}\n\n/** Get the registered provider, or null if none registered. */\nexport function getConfigProvider(): ICreditConfigProvider | null {\n return configProvider;\n}\n\n/** Clear registered provider (for testing). */\nexport function clearConfigProvider(): void {\n configProvider = null;\n}\n","/**\n * Core credit operations - framework agnostic\n *\n * Contains the business logic for credit operations that can be\n * used by any adapter or service implementation.\n */\n\nimport type { ICreditRepository } from \"../repository/types.js\";\nimport type { PortableReservation } from \"./types.js\";\nimport { getOperationLabel } from \"../config/index.js\";\n\n/**\n * Commit a reservation with journal entry\n *\n * @param repository - The credit repository\n * @param userId - User ID\n * @param reservationId - Reservation to commit\n */\nexport async function commitReservationWithJournal(\n repository: ICreditRepository,\n userId: string,\n reservationId: string\n): Promise<void> {\n // Get the reservation to know the amount\n const reservation = await repository.getReservation(userId, reservationId);\n if (!reservation) {\n throw new Error(`Reservation ${reservationId} not found`);\n }\n\n // Commit the reservation atomically\n await repository.commitReservationAtomic(userId, reservationId);\n\n // Create journal entry\n const credits = await repository.getUserCredits(userId);\n if (credits) {\n await repository.createJournalEntry({\n userId,\n entryType: \"debit\",\n amount: reservation.amount,\n balanceAfter: credits.balance,\n source: \"operation_commit\",\n referenceId: reservationId,\n referenceType: \"reservation\",\n description: `Committed ${reservation.amount} credits for ${getOperationLabel(reservation.operationType)}`,\n metadata: {\n operationType: reservation.operationType,\n },\n });\n }\n}\n\n/**\n * Release a reservation with journal entry\n *\n * @param repository - The credit repository\n * @param userId - User ID\n * @param reservationId - Reservation to release\n */\nexport async function releaseReservationWithJournal(\n repository: ICreditRepository,\n userId: string,\n reservationId: string\n): Promise<void> {\n // Get the reservation to check its state\n const reservation = await repository.getReservation(userId, reservationId);\n\n // Release the reservation atomically\n await repository.releaseReservationAtomic(userId, reservationId);\n\n // Create journal entry only if reservation was in reserved state\n if (reservation?.status === \"reserved\") {\n const credits = await repository.getUserCredits(userId);\n if (credits) {\n await repository.createJournalEntry({\n userId,\n entryType: \"credit\",\n amount: 0, // No actual credits returned (they were reserved, not spent)\n balanceAfter: credits.balance,\n source: \"operation_release\",\n referenceId: reservationId,\n referenceType: \"reservation\",\n description: `Released ${reservation.amount} reserved credits for ${getOperationLabel(reservation.operationType)}`,\n metadata: {\n operationType: reservation.operationType,\n amount: reservation.amount,\n },\n });\n }\n }\n}\n\n/**\n * Reserve credits for an operation\n *\n * @param repository - The credit repository\n * @param userId - User ID\n * @param amount - Credits to reserve\n * @param operationType - Type of operation\n * @param expiryMs - Reservation expiry time in milliseconds\n * @returns The reservation\n */\nexport async function reserveCreditsForOperation(\n repository: ICreditRepository,\n userId: string,\n amount: number,\n operationType: string,\n expiryMs: number = 5 * 60 * 1000\n): Promise<PortableReservation> {\n const expiresAt = new Date(Date.now() + expiryMs);\n return repository.reserveCreditsAtomic(userId, amount, operationType, expiresAt);\n}\n","/**\n * Generic credits adapter - framework agnostic\n *\n * This adapter uses standard JavaScript APIs and works in any\n * JavaScript environment (Node.js, browser, etc.).\n */\n\nimport type { PortableReservation, WithCreditsOptions } from \"../core/types.js\";\nimport type { ActionResult, CreditsAdapter, CreditsAdapterConfig, CreditActionHandler } from \"./types.js\";\nimport {\n commitReservationWithJournal,\n releaseReservationWithJournal,\n reserveCreditsForOperation,\n} from \"../core/operations.js\";\n\n/**\n * Generate a unique request ID\n */\nfunction generateRequestId(): string {\n const timestamp = Date.now();\n const randomPart = Math.random().toString(36).substring(2, 14);\n return `req_${timestamp}_${randomPart}`;\n}\n\n/**\n * Create a generic credits adapter\n *\n * This adapter:\n * - Authenticates users via the provided auth provider\n * - Reserves credits before action execution\n * - Commits credits on success, releases on failure\n * - Logs usage asynchronously via the deferred executor\n */\nexport function createGenericAdapter(config: CreditsAdapterConfig): CreditsAdapter {\n const { repository, authProvider, deferred, operationCosts } = config;\n\n function getOperationCost(operationType: string): number {\n const cost = operationCosts[operationType];\n if (cost === undefined) {\n throw new Error(\n `Unknown operation type: \"${operationType}\". ` +\n `Configured types: ${Object.keys(operationCosts).join(\", \")}`\n );\n }\n return cost;\n }\n\n function logUsage(params: {\n userId: string;\n operationType: string;\n creditsUsed: number;\n success: boolean;\n errorMessage?: string;\n resourceId?: string;\n resourceType?: string;\n requestId: string;\n }): void {\n deferred.defer(async () => {\n await repository.logUsage({\n userId: params.userId,\n operationType: params.operationType,\n provider: \"gemini\",\n creditsUsed: params.creditsUsed,\n success: params.success,\n errorMessage: params.errorMessage,\n resourceId: params.resourceId,\n resourceType: params.resourceType,\n requestId: params.requestId,\n });\n });\n }\n\n return {\n withCredits<TInput, TOutput>(\n options: WithCreditsOptions,\n handler: CreditActionHandler<TInput, TOutput>\n ): (data: TInput) => Promise<ActionResult<TOutput>> {\n return async (data: TInput): Promise<ActionResult<TOutput>> => {\n const requestId = generateRequestId();\n\n // Authenticate\n const user = await authProvider.getCurrentUser();\n if (!user?.id) {\n return { success: false, error: \"Authentication required\" };\n }\n\n // Handle preview mode - skip credit handling entirely\n if (isPreviewMode(data)) {\n const dummyReservation = createDummyReservation(user.id, options.operationType);\n return handler(user, data, dummyReservation);\n }\n\n // Calculate cost and reserve credits\n let reservation: PortableReservation;\n try {\n const cost = options.customCost ?? getOperationCost(options.operationType);\n reservation = await reserveCreditsForOperation(\n repository,\n user.id,\n cost,\n options.operationType\n );\n } catch (error) {\n const message = error instanceof Error ? error.message : \"Failed to reserve credits\";\n logUsage({\n userId: user.id,\n operationType: options.operationType,\n creditsUsed: 0,\n success: false,\n errorMessage: message,\n resourceId: options.resourceId,\n resourceType: options.resourceType,\n requestId,\n });\n return { success: false, error: message };\n }\n\n // Execute action\n try {\n const result = await handler(user, data, reservation);\n\n if (result.success) {\n await commitReservationWithJournal(repository, user.id, reservation.id);\n logUsage({\n userId: user.id,\n operationType: options.operationType,\n creditsUsed: reservation.amount,\n success: true,\n resourceId: options.resourceId,\n resourceType: options.resourceType,\n requestId,\n });\n } else {\n await releaseReservationWithJournal(repository, user.id, reservation.id);\n logUsage({\n userId: user.id,\n operationType: options.operationType,\n creditsUsed: 0,\n success: false,\n errorMessage: result.error,\n resourceId: options.resourceId,\n resourceType: options.resourceType,\n requestId,\n });\n }\n\n return result;\n } catch (error) {\n const message = error instanceof Error ? error.message : \"An unexpected error occurred\";\n await releaseReservationWithJournal(repository, user.id, reservation.id);\n logUsage({\n userId: user.id,\n operationType: options.operationType,\n creditsUsed: 0,\n success: false,\n errorMessage: message,\n resourceId: options.resourceId,\n resourceType: options.resourceType,\n requestId,\n });\n console.error(\"Action error:\", error);\n return { success: false, error: message };\n }\n };\n },\n };\n}\n\nfunction isPreviewMode(data: unknown): boolean {\n return (\n typeof data === \"object\" &&\n data !== null &&\n \"preview\" in data &&\n (data as Record<string, unknown>).preview === true\n );\n}\n\nfunction createDummyReservation(userId: string, operationType: string): PortableReservation {\n return {\n id: \"preview-mode\",\n userId,\n amount: 0,\n operationType,\n status: \"released\",\n createdAt: new Date().toISOString(),\n expiresAt: new Date().toISOString(),\n };\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"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 {\n ActionResult,\n CreditActionHandler,\n CreditsAdapterConfig,\n CreditsAdapter,\n} from \"./types.js\";\n\nexport { createGenericAdapter } from \"./generic.js\";\n","import { z } from \"zod\";\nimport type { SubscriptionTier, TierConfig } from \"../core/types.js\";\nimport { getConfigProvider } from \"./provider.js\";\n\n/**\n * Schema for tier configuration\n */\nconst tierConfigSchema = z.object({\n tier: z.string().min(1),\n monthlyCredits: z.number().min(0),\n priceUsd: z.number().min(0),\n features: z.array(z.string()),\n isFree: z.boolean().optional(),\n unlimited: z.boolean().optional(),\n isDefault: z.boolean().optional(),\n});\n\n/**\n * Schema for credit system configuration\n */\nconst creditSystemConfigSchema = z.object({\n /**\n * Operation costs in credits\n * Keys are operation type names (any non-empty string)\n * Values are positive numbers representing credit cost\n */\n operationCosts: z.record(\n z.string().min(1),\n z.number().positive()\n ),\n\n /**\n * Display labels for operation types (English canonical labels).\n * Falls back to auto-generating from snake_case keys if not provided.\n */\n operationLabels: z.record(\n z.string().min(1),\n z.string().min(1)\n ).optional().default({}),\n\n /**\n * Tier configurations\n */\n tierConfigs: z.record(\n z.string().min(1),\n tierConfigSchema\n ),\n\n /**\n * Reservation expiry time in milliseconds\n */\n reservationExpiryMs: z.number().positive().default(5 * 60 * 1000),\n\n /**\n * Default credits for new users (free tier)\n */\n defaultFreeCredits: z.number().positive().default(25),\n\n /**\n * Grace period in days after subscription expires before downgrade\n */\n subscriptionGracePeriodDays: z.number().min(0).default(3),\n\n /**\n * Low balance notification threshold\n */\n lowBalanceThreshold: z.number().min(0).default(10),\n\n /**\n * Cooldown in hours between low balance notifications\n */\n lowBalanceNotificationCooldownHours: z.number().positive().default(24),\n\n /**\n * Feature flags for optional features\n */\n features: z.object({\n journalEntries: z.boolean().default(true),\n notifications: z.boolean().default(true),\n subscriptionExpiry: z.boolean().default(true),\n usageHistory: z.boolean().default(true),\n }).default({\n journalEntries: true,\n notifications: true,\n subscriptionExpiry: true,\n usageHistory: true,\n }),\n});\n\n/**\n * Credit system configuration type\n */\nexport type CreditSystemConfig = z.infer<typeof creditSystemConfigSchema>;\n\n/**\n * Default configuration\n */\nconst DEFAULT_CONFIG: CreditSystemConfig = {\n operationCosts: {\n story_generation: 5,\n conversation: 2,\n image_generation: 10,\n template_generation: 10,\n },\n operationLabels: {\n story_generation: \"Story generation\",\n conversation: \"Conversation\",\n image_generation: \"Image generation\",\n template_generation: \"Template generation\",\n },\n tierConfigs: {\n free: {\n tier: \"free\",\n monthlyCredits: 25,\n priceUsd: 0,\n isFree: true,\n isDefault: true,\n features: [\n \"25 credits per month\",\n \"Basic story generation\",\n \"Standard image quality\",\n ],\n },\n basic: {\n tier: \"basic\",\n monthlyCredits: 200,\n priceUsd: 9.99,\n features: [\n \"200 credits per month\",\n \"All story features\",\n \"High quality images\",\n \"Priority support\",\n ],\n },\n premium: {\n tier: \"premium\",\n monthlyCredits: 500,\n priceUsd: 19.99,\n features: [\n \"500 credits per month\",\n \"All features\",\n \"Highest quality images\",\n \"Early access to new features\",\n \"Priority support\",\n ],\n },\n unlimited: {\n tier: \"unlimited\",\n monthlyCredits: 0, // 0 = unlimited\n unlimited: true,\n priceUsd: 49.99,\n features: [\n \"Unlimited credits\",\n \"All features\",\n \"Highest quality images\",\n \"Early access to new features\",\n \"Dedicated support\",\n ],\n },\n },\n reservationExpiryMs: 5 * 60 * 1000, // 5 minutes\n defaultFreeCredits: 25,\n subscriptionGracePeriodDays: 3,\n lowBalanceThreshold: 10,\n lowBalanceNotificationCooldownHours: 24,\n features: {\n journalEntries: true,\n notifications: true,\n subscriptionExpiry: true,\n usageHistory: true,\n },\n};\n\n/**\n * Current configuration (can be overridden)\n */\nlet currentConfig: CreditSystemConfig = DEFAULT_CONFIG;\n\n/**\n * Load configuration from environment variables\n * Can be extended to load from other sources (file, remote config, etc.)\n *\n * Operation costs are loaded from CREDITS_OPERATION_COSTS as JSON:\n * CREDITS_OPERATION_COSTS={\"story_generation\":5,\"image_generation\":10}\n */\nexport function loadConfigFromEnv(): Partial<CreditSystemConfig> {\n const envConfig: Partial<CreditSystemConfig> = {};\n\n // Load operation costs from env as JSON\n const operationCostsEnv = process.env.CREDITS_OPERATION_COSTS;\n if (operationCostsEnv) {\n try {\n const parsed = JSON.parse(operationCostsEnv);\n if (typeof parsed === \"object\" && parsed !== null) {\n envConfig.operationCosts = {\n ...DEFAULT_CONFIG.operationCosts,\n ...parsed,\n };\n }\n } catch {\n console.warn(\"[Credits Config] Failed to parse CREDITS_OPERATION_COSTS env var as JSON\");\n }\n }\n\n // Load other settings from env\n const reservationExpiry = process.env.CREDITS_RESERVATION_EXPIRY_MS;\n if (reservationExpiry) {\n envConfig.reservationExpiryMs = parseInt(reservationExpiry, 10);\n }\n\n const defaultCredits = process.env.CREDITS_DEFAULT_FREE;\n if (defaultCredits) {\n envConfig.defaultFreeCredits = parseInt(defaultCredits, 10);\n }\n\n const gracePeriod = process.env.CREDITS_GRACE_PERIOD_DAYS;\n if (gracePeriod) {\n envConfig.subscriptionGracePeriodDays = parseInt(gracePeriod, 10);\n }\n\n const lowBalanceThreshold = process.env.CREDITS_LOW_BALANCE_THRESHOLD;\n if (lowBalanceThreshold) {\n envConfig.lowBalanceThreshold = parseInt(lowBalanceThreshold, 10);\n }\n\n // Load feature flags\n const featuresEnv = process.env.CREDITS_FEATURES;\n if (featuresEnv) {\n try {\n const features = JSON.parse(featuresEnv);\n envConfig.features = {\n ...DEFAULT_CONFIG.features,\n ...features,\n };\n } catch {\n console.warn(\"[Credits Config] Failed to parse CREDITS_FEATURES env var\");\n }\n }\n\n return envConfig;\n}\n\n/**\n * Initialize configuration\n * Merges default config with environment overrides\n */\nexport function initializeConfig(overrides?: Partial<CreditSystemConfig>): CreditSystemConfig {\n const envConfig = loadConfigFromEnv();\n\n const mergedConfig = {\n ...DEFAULT_CONFIG,\n ...envConfig,\n ...overrides,\n operationCosts: {\n ...DEFAULT_CONFIG.operationCosts,\n ...envConfig.operationCosts,\n ...overrides?.operationCosts,\n },\n operationLabels: {\n ...DEFAULT_CONFIG.operationLabels,\n ...envConfig.operationLabels,\n ...overrides?.operationLabels,\n },\n tierConfigs: {\n ...DEFAULT_CONFIG.tierConfigs,\n ...envConfig.tierConfigs,\n ...overrides?.tierConfigs,\n },\n features: {\n ...DEFAULT_CONFIG.features,\n ...envConfig.features,\n ...overrides?.features,\n },\n };\n\n // Validate configuration\n const result = creditSystemConfigSchema.safeParse(mergedConfig);\n if (!result.success) {\n console.error(\"[Credits Config] Invalid configuration:\", result.error.issues);\n throw new Error(\"Invalid credit system configuration\");\n }\n\n currentConfig = result.data;\n return currentConfig;\n}\n\n/**\n * Get the current configuration.\n * If an external provider is registered, delegates to it.\n * Otherwise falls back to the local currentConfig.\n */\nexport function getConfig(): CreditSystemConfig {\n const provider = getConfigProvider();\n if (provider) {\n return provider.getConfig();\n }\n return currentConfig;\n}\n\n/**\n * Get all valid operation types from configuration\n * Returns the keys of the operationCosts object\n */\nexport function getValidOperationTypes(): string[] {\n return Object.keys(getConfig().operationCosts);\n}\n\n/**\n * Check if an operation type is valid (configured in the system)\n * @param type - The operation type to check\n * @returns true if the operation type is configured\n */\nexport function isValidOperationType(type: string): boolean {\n return type in getConfig().operationCosts;\n}\n\n/**\n * Get operation cost from configuration\n * @throws Error if the operation type is not configured\n */\nexport function getConfigOperationCost(operationType: string): number {\n const cost = getConfig().operationCosts[operationType];\n if (cost === undefined) {\n throw new Error(\n `Unknown operation type: ${operationType}. Valid types: ${getValidOperationTypes().join(\", \")}`\n );\n }\n return cost;\n}\n\n/**\n * Get all valid tier ids from configuration\n * Returns the keys of the tierConfigs object\n */\nexport function getValidTiers(): string[] {\n return Object.keys(getConfig().tierConfigs);\n}\n\n/**\n * Check if a tier id is valid (configured in the system)\n */\nexport function isValidTier(tier: string): boolean {\n return tier in getConfig().tierConfigs;\n}\n\n/**\n * Check if a tier is the free/default tier.\n * Falls back to priceUsd === 0 when the isFree flag is not set.\n */\nexport function isFreeTier(tier: SubscriptionTier): boolean {\n const c = getConfig().tierConfigs[tier];\n if (!c) return false;\n return c.isFree ?? c.priceUsd === 0;\n}\n\n/**\n * Check if a tier is unlimited.\n * Falls back to monthlyCredits === 0 when the unlimited flag is not set.\n */\nexport function isUnlimitedTier(tier: SubscriptionTier): boolean {\n const c = getConfig().tierConfigs[tier];\n if (!c) return false;\n return c.unlimited ?? c.monthlyCredits === 0;\n}\n\n/**\n * Get the tier assigned to brand-new users.\n * Prefers the tier flagged isDefault, then the first isFree tier, then \"free\".\n */\nexport function getDefaultTier(): SubscriptionTier {\n const entries = Object.entries(getConfig().tierConfigs);\n const def =\n entries.find(([, c]) => c.isDefault)?.[0] ??\n entries.find(([, c]) => c.isFree ?? c.priceUsd === 0)?.[0];\n return def ?? \"free\";\n}\n\n/**\n * Magic balance assigned to unlimited tiers on upgrade.\n * Kept at 999999 for back-compat with stored/displayed balances.\n */\nexport const UNLIMITED_BALANCE_SENTINEL = 999999;\n\n/**\n * Get the magic balance assigned to unlimited tiers on upgrade.\n */\nexport function getUnlimitedSentinelBalance(): number {\n return UNLIMITED_BALANCE_SENTINEL;\n}\n\n/**\n * Get tier configuration from configuration.\n * Falls back to the default tier (with a warning) if the tier is unknown.\n */\nexport function getConfigTierConfig(tier: SubscriptionTier): TierConfig {\n const cfg = getConfig().tierConfigs[tier];\n if (cfg) return cfg;\n const fallback = getDefaultTier();\n console.warn(\n `[Credits Config] Unknown tier \"${tier}\". Falling back to \"${fallback}\". Valid: ${getValidTiers().join(\", \")}`\n );\n return getConfig().tierConfigs[fallback]!;\n}\n\n/**\n * Get monthly limit for a tier from configuration\n * Returns Infinity for unlimited tier\n */\nexport function getConfigMonthlyLimit(tier: SubscriptionTier): number {\n return isUnlimitedTier(tier) ? Infinity : getConfigTierConfig(tier).monthlyCredits;\n}\n\n/**\n * Runtime zod validator for tier ids. Validates against the LIVE config keys\n * at parse time, so apps that add tiers via config get correct validation.\n */\nexport const tierSchema = z\n .string()\n .refine(isValidTier, { message: \"Unknown subscription tier\" }) as unknown as z.ZodType<SubscriptionTier>;\n\n/**\n * Parse/validate a tier id against the live config keys.\n * @throws ZodError if the tier is not configured\n */\nexport function parseTier(value: unknown): SubscriptionTier {\n return tierSchema.parse(value);\n}\n\n/**\n * Check if a feature is enabled\n */\nexport function isFeatureEnabled(feature: keyof CreditSystemConfig[\"features\"]): boolean {\n return getConfig().features[feature];\n}\n\n/**\n * Convert a snake_case string to Title Case.\n * E.g., \"story_generation\" -> \"Story Generation\"\n */\nfunction snakeCaseToTitleCase(str: string): string {\n return str\n .replace(/_/g, \" \")\n .replace(/\\b\\w/g, (c) => c.toUpperCase());\n}\n\n/**\n * Get the display label for an operation type.\n * Falls back to converting snake_case to Title Case if no label is configured.\n */\nexport function getOperationLabel(operationType: string): string {\n const labels = getConfig().operationLabels;\n if (labels && operationType in labels) {\n return labels[operationType];\n }\n return snakeCaseToTitleCase(operationType);\n}\n\n/**\n * Get all operation labels as a record.\n * For any operation in operationCosts that lacks a label, generates one from the key.\n */\nexport function getOperationLabels(): Record<string, string> {\n const config = getConfig();\n const result: Record<string, string> = {};\n for (const key of Object.keys(config.operationCosts)) {\n result[key] = config.operationLabels?.[key]\n ?? snakeCaseToTitleCase(key);\n }\n return result;\n}\n\n/**\n * Reset configuration to defaults (for testing)\n */\nexport function resetConfig(): void {\n currentConfig = DEFAULT_CONFIG;\n}\n\n// Initialize configuration on module load\ninitializeConfig();\n","import type { CreditSystemConfig } from \"./index.js\";\n\n/** Interface for external config providers (e.g. Firestore-backed) */\nexport interface ICreditConfigProvider {\n getConfig(): CreditSystemConfig;\n}\n\nlet configProvider: ICreditConfigProvider | null = null;\n\n/** Register an external config provider. Called once at app startup. */\nexport function registerConfigProvider(provider: ICreditConfigProvider): void {\n configProvider = provider;\n}\n\n/** Get the registered provider, or null if none registered. */\nexport function getConfigProvider(): ICreditConfigProvider | null {\n return configProvider;\n}\n\n/** Clear registered provider (for testing). */\nexport function clearConfigProvider(): void {\n configProvider = null;\n}\n","/**\n * Core credit operations - framework agnostic\n *\n * Contains the business logic for credit operations that can be\n * used by any adapter or service implementation.\n */\n\nimport type { ICreditRepository } from \"../repository/types.js\";\nimport type { PortableReservation } from \"./types.js\";\nimport { getOperationLabel } from \"../config/index.js\";\n\n/**\n * Commit a reservation with journal entry\n *\n * @param repository - The credit repository\n * @param userId - User ID\n * @param reservationId - Reservation to commit\n */\nexport async function commitReservationWithJournal(\n repository: ICreditRepository,\n userId: string,\n reservationId: string\n): Promise<void> {\n // Get the reservation to know the amount\n const reservation = await repository.getReservation(userId, reservationId);\n if (!reservation) {\n throw new Error(`Reservation ${reservationId} not found`);\n }\n\n // Commit the reservation atomically\n await repository.commitReservationAtomic(userId, reservationId);\n\n // Create journal entry\n const credits = await repository.getUserCredits(userId);\n if (credits) {\n await repository.createJournalEntry({\n userId,\n entryType: \"debit\",\n amount: reservation.amount,\n balanceAfter: credits.balance,\n source: \"operation_commit\",\n referenceId: reservationId,\n referenceType: \"reservation\",\n description: `Committed ${reservation.amount} credits for ${getOperationLabel(reservation.operationType)}`,\n metadata: {\n operationType: reservation.operationType,\n },\n });\n }\n}\n\n/**\n * Release a reservation with journal entry\n *\n * @param repository - The credit repository\n * @param userId - User ID\n * @param reservationId - Reservation to release\n */\nexport async function releaseReservationWithJournal(\n repository: ICreditRepository,\n userId: string,\n reservationId: string\n): Promise<void> {\n // Get the reservation to check its state\n const reservation = await repository.getReservation(userId, reservationId);\n\n // Release the reservation atomically\n await repository.releaseReservationAtomic(userId, reservationId);\n\n // Create journal entry only if reservation was in reserved state\n if (reservation?.status === \"reserved\") {\n const credits = await repository.getUserCredits(userId);\n if (credits) {\n await repository.createJournalEntry({\n userId,\n entryType: \"credit\",\n amount: 0, // No actual credits returned (they were reserved, not spent)\n balanceAfter: credits.balance,\n source: \"operation_release\",\n referenceId: reservationId,\n referenceType: \"reservation\",\n description: `Released ${reservation.amount} reserved credits for ${getOperationLabel(reservation.operationType)}`,\n metadata: {\n operationType: reservation.operationType,\n amount: reservation.amount,\n },\n });\n }\n }\n}\n\n/**\n * Reserve credits for an operation\n *\n * @param repository - The credit repository\n * @param userId - User ID\n * @param amount - Credits to reserve\n * @param operationType - Type of operation\n * @param expiryMs - Reservation expiry time in milliseconds\n * @returns The reservation\n */\nexport async function reserveCreditsForOperation(\n repository: ICreditRepository,\n userId: string,\n amount: number,\n operationType: string,\n expiryMs: number = 5 * 60 * 1000\n): Promise<PortableReservation> {\n const expiresAt = new Date(Date.now() + expiryMs);\n return repository.reserveCreditsAtomic(userId, amount, operationType, expiresAt);\n}\n","/**\n * Generic credits adapter - framework agnostic\n *\n * This adapter uses standard JavaScript APIs and works in any\n * JavaScript environment (Node.js, browser, etc.).\n */\n\nimport type { PortableReservation, WithCreditsOptions } from \"../core/types.js\";\nimport type { ActionResult, CreditsAdapter, CreditsAdapterConfig, CreditActionHandler } from \"./types.js\";\nimport {\n commitReservationWithJournal,\n releaseReservationWithJournal,\n reserveCreditsForOperation,\n} from \"../core/operations.js\";\n\n/**\n * Generate a unique request ID\n */\nfunction generateRequestId(): string {\n const timestamp = Date.now();\n const randomPart = Math.random().toString(36).substring(2, 14);\n return `req_${timestamp}_${randomPart}`;\n}\n\n/**\n * Create a generic credits adapter\n *\n * This adapter:\n * - Authenticates users via the provided auth provider\n * - Reserves credits before action execution\n * - Commits credits on success, releases on failure\n * - Logs usage asynchronously via the deferred executor\n */\nexport function createGenericAdapter(config: CreditsAdapterConfig): CreditsAdapter {\n const { repository, authProvider, deferred, operationCosts } = config;\n\n function getOperationCost(operationType: string): number {\n const cost = operationCosts[operationType];\n if (cost === undefined) {\n throw new Error(\n `Unknown operation type: \"${operationType}\". ` +\n `Configured types: ${Object.keys(operationCosts).join(\", \")}`\n );\n }\n return cost;\n }\n\n function logUsage(params: {\n userId: string;\n operationType: string;\n creditsUsed: number;\n success: boolean;\n errorMessage?: string;\n resourceId?: string;\n resourceType?: string;\n requestId: string;\n }): void {\n deferred.defer(async () => {\n await repository.logUsage({\n userId: params.userId,\n operationType: params.operationType,\n provider: \"gemini\",\n creditsUsed: params.creditsUsed,\n success: params.success,\n errorMessage: params.errorMessage,\n resourceId: params.resourceId,\n resourceType: params.resourceType,\n requestId: params.requestId,\n });\n });\n }\n\n return {\n withCredits<TInput, TOutput>(\n options: WithCreditsOptions,\n handler: CreditActionHandler<TInput, TOutput>\n ): (data: TInput) => Promise<ActionResult<TOutput>> {\n return async (data: TInput): Promise<ActionResult<TOutput>> => {\n const requestId = generateRequestId();\n\n // Authenticate\n const user = await authProvider.getCurrentUser();\n if (!user?.id) {\n return { success: false, error: \"Authentication required\" };\n }\n\n // Handle preview mode - skip credit handling entirely\n if (isPreviewMode(data)) {\n const dummyReservation = createDummyReservation(user.id, options.operationType);\n return handler(user, data, dummyReservation);\n }\n\n // Calculate cost and reserve credits\n let reservation: PortableReservation;\n try {\n const cost = options.customCost ?? getOperationCost(options.operationType);\n reservation = await reserveCreditsForOperation(\n repository,\n user.id,\n cost,\n options.operationType\n );\n } catch (error) {\n const message = error instanceof Error ? error.message : \"Failed to reserve credits\";\n logUsage({\n userId: user.id,\n operationType: options.operationType,\n creditsUsed: 0,\n success: false,\n errorMessage: message,\n resourceId: options.resourceId,\n resourceType: options.resourceType,\n requestId,\n });\n return { success: false, error: message };\n }\n\n // Execute action\n try {\n const result = await handler(user, data, reservation);\n\n if (result.success) {\n await commitReservationWithJournal(repository, user.id, reservation.id);\n logUsage({\n userId: user.id,\n operationType: options.operationType,\n creditsUsed: reservation.amount,\n success: true,\n resourceId: options.resourceId,\n resourceType: options.resourceType,\n requestId,\n });\n } else {\n await releaseReservationWithJournal(repository, user.id, reservation.id);\n logUsage({\n userId: user.id,\n operationType: options.operationType,\n creditsUsed: 0,\n success: false,\n errorMessage: result.error,\n resourceId: options.resourceId,\n resourceType: options.resourceType,\n requestId,\n });\n }\n\n return result;\n } catch (error) {\n const message = error instanceof Error ? error.message : \"An unexpected error occurred\";\n await releaseReservationWithJournal(repository, user.id, reservation.id);\n logUsage({\n userId: user.id,\n operationType: options.operationType,\n creditsUsed: 0,\n success: false,\n errorMessage: message,\n resourceId: options.resourceId,\n resourceType: options.resourceType,\n requestId,\n });\n console.error(\"Action error:\", error);\n return { success: false, error: message };\n }\n };\n },\n };\n}\n\nfunction isPreviewMode(data: unknown): boolean {\n return (\n typeof data === \"object\" &&\n data !== null &&\n \"preview\" in data &&\n (data as Record<string, unknown>).preview === true\n );\n}\n\nfunction createDummyReservation(userId: string, operationType: string): PortableReservation {\n return {\n id: \"preview-mode\",\n userId,\n amount: 0,\n operationType,\n status: \"released\",\n createdAt: new Date().toISOString(),\n expiresAt: new Date().toISOString(),\n };\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { c as PortableReservation, W as WithCreditsOptions } from '../types-
|
|
1
|
+
import { c as PortableReservation, W as WithCreditsOptions } from '../types-CjLj3GW5.cjs';
|
|
2
2
|
import { a as CreditsUser, I as ICreditsAuthProvider } from '../types-OZU3I5OM.cjs';
|
|
3
|
-
import { I as ICreditRepository } from '../types-
|
|
3
|
+
import { I as ICreditRepository } from '../types-CZNv1U3e.cjs';
|
|
4
4
|
import { D as DeferredExecutor } from '../deferred-C7rrRfkm.cjs';
|
|
5
5
|
|
|
6
6
|
/**
|
package/dist/adapters/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { c as PortableReservation, W as WithCreditsOptions } from '../types-
|
|
1
|
+
import { c as PortableReservation, W as WithCreditsOptions } from '../types-CjLj3GW5.js';
|
|
2
2
|
import { a as CreditsUser, I as ICreditsAuthProvider } from '../types-OZU3I5OM.js';
|
|
3
|
-
import { I as ICreditRepository } from '../types-
|
|
3
|
+
import { I as ICreditRepository } from '../types-G4WK0rM-.js';
|
|
4
4
|
import { D as DeferredExecutor } from '../deferred-C7rrRfkm.js';
|
|
5
5
|
|
|
6
6
|
/**
|
package/dist/adapters/index.js
CHANGED
|
@@ -9,10 +9,13 @@ function getConfigProvider() {
|
|
|
9
9
|
|
|
10
10
|
// src/config/index.ts
|
|
11
11
|
var tierConfigSchema = z.object({
|
|
12
|
-
tier: z.
|
|
12
|
+
tier: z.string().min(1),
|
|
13
13
|
monthlyCredits: z.number().min(0),
|
|
14
14
|
priceUsd: z.number().min(0),
|
|
15
|
-
features: z.array(z.string())
|
|
15
|
+
features: z.array(z.string()),
|
|
16
|
+
isFree: z.boolean().optional(),
|
|
17
|
+
unlimited: z.boolean().optional(),
|
|
18
|
+
isDefault: z.boolean().optional()
|
|
16
19
|
});
|
|
17
20
|
var creditSystemConfigSchema = z.object({
|
|
18
21
|
/**
|
|
@@ -36,7 +39,7 @@ var creditSystemConfigSchema = z.object({
|
|
|
36
39
|
* Tier configurations
|
|
37
40
|
*/
|
|
38
41
|
tierConfigs: z.record(
|
|
39
|
-
z.
|
|
42
|
+
z.string().min(1),
|
|
40
43
|
tierConfigSchema
|
|
41
44
|
),
|
|
42
45
|
/**
|
|
@@ -92,6 +95,8 @@ var DEFAULT_CONFIG = {
|
|
|
92
95
|
tier: "free",
|
|
93
96
|
monthlyCredits: 25,
|
|
94
97
|
priceUsd: 0,
|
|
98
|
+
isFree: true,
|
|
99
|
+
isDefault: true,
|
|
95
100
|
features: [
|
|
96
101
|
"25 credits per month",
|
|
97
102
|
"Basic story generation",
|
|
@@ -125,6 +130,7 @@ var DEFAULT_CONFIG = {
|
|
|
125
130
|
tier: "unlimited",
|
|
126
131
|
monthlyCredits: 0,
|
|
127
132
|
// 0 = unlimited
|
|
133
|
+
unlimited: true,
|
|
128
134
|
priceUsd: 49.99,
|
|
129
135
|
features: [
|
|
130
136
|
"Unlimited credits",
|
|
@@ -237,6 +243,10 @@ function getConfig() {
|
|
|
237
243
|
}
|
|
238
244
|
return currentConfig;
|
|
239
245
|
}
|
|
246
|
+
function isValidTier(tier) {
|
|
247
|
+
return tier in getConfig().tierConfigs;
|
|
248
|
+
}
|
|
249
|
+
var tierSchema = z.string().refine(isValidTier, { message: "Unknown subscription tier" });
|
|
240
250
|
function snakeCaseToTitleCase(str) {
|
|
241
251
|
return str.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
242
252
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/config/index.ts","../../src/config/provider.ts","../../src/core/operations.ts","../../src/adapters/generic.ts"],"sourcesContent":["import { z } from \"zod\";\nimport type { SubscriptionTier, TierConfig } from \"../core/types.js\";\nimport { getConfigProvider } from \"./provider.js\";\n\n/**\n * Schema for tier configuration\n */\nconst tierConfigSchema = z.object({\n tier: z.enum([\"free\", \"basic\", \"premium\", \"unlimited\"]),\n monthlyCredits: z.number().min(0),\n priceUsd: z.number().min(0),\n features: z.array(z.string()),\n});\n\n/**\n * Schema for credit system configuration\n */\nconst creditSystemConfigSchema = z.object({\n /**\n * Operation costs in credits\n * Keys are operation type names (any non-empty string)\n * Values are positive numbers representing credit cost\n */\n operationCosts: z.record(\n z.string().min(1),\n z.number().positive()\n ),\n\n /**\n * Display labels for operation types (English canonical labels).\n * Falls back to auto-generating from snake_case keys if not provided.\n */\n operationLabels: z.record(\n z.string().min(1),\n z.string().min(1)\n ).optional().default({}),\n\n /**\n * Tier configurations\n */\n tierConfigs: z.record(\n z.enum([\"free\", \"basic\", \"premium\", \"unlimited\"]),\n tierConfigSchema\n ),\n\n /**\n * Reservation expiry time in milliseconds\n */\n reservationExpiryMs: z.number().positive().default(5 * 60 * 1000),\n\n /**\n * Default credits for new users (free tier)\n */\n defaultFreeCredits: z.number().positive().default(25),\n\n /**\n * Grace period in days after subscription expires before downgrade\n */\n subscriptionGracePeriodDays: z.number().min(0).default(3),\n\n /**\n * Low balance notification threshold\n */\n lowBalanceThreshold: z.number().min(0).default(10),\n\n /**\n * Cooldown in hours between low balance notifications\n */\n lowBalanceNotificationCooldownHours: z.number().positive().default(24),\n\n /**\n * Feature flags for optional features\n */\n features: z.object({\n journalEntries: z.boolean().default(true),\n notifications: z.boolean().default(true),\n subscriptionExpiry: z.boolean().default(true),\n usageHistory: z.boolean().default(true),\n }).default({\n journalEntries: true,\n notifications: true,\n subscriptionExpiry: true,\n usageHistory: true,\n }),\n});\n\n/**\n * Credit system configuration type\n */\nexport type CreditSystemConfig = z.infer<typeof creditSystemConfigSchema>;\n\n/**\n * Default configuration\n */\nconst DEFAULT_CONFIG: CreditSystemConfig = {\n operationCosts: {\n story_generation: 5,\n conversation: 2,\n image_generation: 10,\n template_generation: 10,\n },\n operationLabels: {\n story_generation: \"Story generation\",\n conversation: \"Conversation\",\n image_generation: \"Image generation\",\n template_generation: \"Template generation\",\n },\n tierConfigs: {\n free: {\n tier: \"free\",\n monthlyCredits: 25,\n priceUsd: 0,\n features: [\n \"25 credits per month\",\n \"Basic story generation\",\n \"Standard image quality\",\n ],\n },\n basic: {\n tier: \"basic\",\n monthlyCredits: 200,\n priceUsd: 9.99,\n features: [\n \"200 credits per month\",\n \"All story features\",\n \"High quality images\",\n \"Priority support\",\n ],\n },\n premium: {\n tier: \"premium\",\n monthlyCredits: 500,\n priceUsd: 19.99,\n features: [\n \"500 credits per month\",\n \"All features\",\n \"Highest quality images\",\n \"Early access to new features\",\n \"Priority support\",\n ],\n },\n unlimited: {\n tier: \"unlimited\",\n monthlyCredits: 0, // 0 = unlimited\n priceUsd: 49.99,\n features: [\n \"Unlimited credits\",\n \"All features\",\n \"Highest quality images\",\n \"Early access to new features\",\n \"Dedicated support\",\n ],\n },\n },\n reservationExpiryMs: 5 * 60 * 1000, // 5 minutes\n defaultFreeCredits: 25,\n subscriptionGracePeriodDays: 3,\n lowBalanceThreshold: 10,\n lowBalanceNotificationCooldownHours: 24,\n features: {\n journalEntries: true,\n notifications: true,\n subscriptionExpiry: true,\n usageHistory: true,\n },\n};\n\n/**\n * Current configuration (can be overridden)\n */\nlet currentConfig: CreditSystemConfig = DEFAULT_CONFIG;\n\n/**\n * Load configuration from environment variables\n * Can be extended to load from other sources (file, remote config, etc.)\n *\n * Operation costs are loaded from CREDITS_OPERATION_COSTS as JSON:\n * CREDITS_OPERATION_COSTS={\"story_generation\":5,\"image_generation\":10}\n */\nexport function loadConfigFromEnv(): Partial<CreditSystemConfig> {\n const envConfig: Partial<CreditSystemConfig> = {};\n\n // Load operation costs from env as JSON\n const operationCostsEnv = process.env.CREDITS_OPERATION_COSTS;\n if (operationCostsEnv) {\n try {\n const parsed = JSON.parse(operationCostsEnv);\n if (typeof parsed === \"object\" && parsed !== null) {\n envConfig.operationCosts = {\n ...DEFAULT_CONFIG.operationCosts,\n ...parsed,\n };\n }\n } catch {\n console.warn(\"[Credits Config] Failed to parse CREDITS_OPERATION_COSTS env var as JSON\");\n }\n }\n\n // Load other settings from env\n const reservationExpiry = process.env.CREDITS_RESERVATION_EXPIRY_MS;\n if (reservationExpiry) {\n envConfig.reservationExpiryMs = parseInt(reservationExpiry, 10);\n }\n\n const defaultCredits = process.env.CREDITS_DEFAULT_FREE;\n if (defaultCredits) {\n envConfig.defaultFreeCredits = parseInt(defaultCredits, 10);\n }\n\n const gracePeriod = process.env.CREDITS_GRACE_PERIOD_DAYS;\n if (gracePeriod) {\n envConfig.subscriptionGracePeriodDays = parseInt(gracePeriod, 10);\n }\n\n const lowBalanceThreshold = process.env.CREDITS_LOW_BALANCE_THRESHOLD;\n if (lowBalanceThreshold) {\n envConfig.lowBalanceThreshold = parseInt(lowBalanceThreshold, 10);\n }\n\n // Load feature flags\n const featuresEnv = process.env.CREDITS_FEATURES;\n if (featuresEnv) {\n try {\n const features = JSON.parse(featuresEnv);\n envConfig.features = {\n ...DEFAULT_CONFIG.features,\n ...features,\n };\n } catch {\n console.warn(\"[Credits Config] Failed to parse CREDITS_FEATURES env var\");\n }\n }\n\n return envConfig;\n}\n\n/**\n * Initialize configuration\n * Merges default config with environment overrides\n */\nexport function initializeConfig(overrides?: Partial<CreditSystemConfig>): CreditSystemConfig {\n const envConfig = loadConfigFromEnv();\n\n const mergedConfig = {\n ...DEFAULT_CONFIG,\n ...envConfig,\n ...overrides,\n operationCosts: {\n ...DEFAULT_CONFIG.operationCosts,\n ...envConfig.operationCosts,\n ...overrides?.operationCosts,\n },\n operationLabels: {\n ...DEFAULT_CONFIG.operationLabels,\n ...envConfig.operationLabels,\n ...overrides?.operationLabels,\n },\n tierConfigs: {\n ...DEFAULT_CONFIG.tierConfigs,\n ...envConfig.tierConfigs,\n ...overrides?.tierConfigs,\n },\n features: {\n ...DEFAULT_CONFIG.features,\n ...envConfig.features,\n ...overrides?.features,\n },\n };\n\n // Validate configuration\n const result = creditSystemConfigSchema.safeParse(mergedConfig);\n if (!result.success) {\n console.error(\"[Credits Config] Invalid configuration:\", result.error.issues);\n throw new Error(\"Invalid credit system configuration\");\n }\n\n currentConfig = result.data;\n return currentConfig;\n}\n\n/**\n * Get the current configuration.\n * If an external provider is registered, delegates to it.\n * Otherwise falls back to the local currentConfig.\n */\nexport function getConfig(): CreditSystemConfig {\n const provider = getConfigProvider();\n if (provider) {\n return provider.getConfig();\n }\n return currentConfig;\n}\n\n/**\n * Get all valid operation types from configuration\n * Returns the keys of the operationCosts object\n */\nexport function getValidOperationTypes(): string[] {\n return Object.keys(getConfig().operationCosts);\n}\n\n/**\n * Check if an operation type is valid (configured in the system)\n * @param type - The operation type to check\n * @returns true if the operation type is configured\n */\nexport function isValidOperationType(type: string): boolean {\n return type in getConfig().operationCosts;\n}\n\n/**\n * Get operation cost from configuration\n * @throws Error if the operation type is not configured\n */\nexport function getConfigOperationCost(operationType: string): number {\n const cost = getConfig().operationCosts[operationType];\n if (cost === undefined) {\n throw new Error(\n `Unknown operation type: ${operationType}. Valid types: ${getValidOperationTypes().join(\", \")}`\n );\n }\n return cost;\n}\n\n/**\n * Get tier configuration from configuration\n */\nexport function getConfigTierConfig(tier: SubscriptionTier): TierConfig {\n return getConfig().tierConfigs[tier]!;\n}\n\n/**\n * Get monthly limit for a tier from configuration\n * Returns Infinity for unlimited tier\n */\nexport function getConfigMonthlyLimit(tier: SubscriptionTier): number {\n const config = getConfig().tierConfigs[tier]!;\n return config.monthlyCredits === 0 ? Infinity : config.monthlyCredits;\n}\n\n/**\n * Check if a feature is enabled\n */\nexport function isFeatureEnabled(feature: keyof CreditSystemConfig[\"features\"]): boolean {\n return getConfig().features[feature];\n}\n\n/**\n * Convert a snake_case string to Title Case.\n * E.g., \"story_generation\" -> \"Story Generation\"\n */\nfunction snakeCaseToTitleCase(str: string): string {\n return str\n .replace(/_/g, \" \")\n .replace(/\\b\\w/g, (c) => c.toUpperCase());\n}\n\n/**\n * Get the display label for an operation type.\n * Falls back to converting snake_case to Title Case if no label is configured.\n */\nexport function getOperationLabel(operationType: string): string {\n const labels = getConfig().operationLabels;\n if (labels && operationType in labels) {\n return labels[operationType];\n }\n return snakeCaseToTitleCase(operationType);\n}\n\n/**\n * Get all operation labels as a record.\n * For any operation in operationCosts that lacks a label, generates one from the key.\n */\nexport function getOperationLabels(): Record<string, string> {\n const config = getConfig();\n const result: Record<string, string> = {};\n for (const key of Object.keys(config.operationCosts)) {\n result[key] = config.operationLabels?.[key]\n ?? snakeCaseToTitleCase(key);\n }\n return result;\n}\n\n/**\n * Reset configuration to defaults (for testing)\n */\nexport function resetConfig(): void {\n currentConfig = DEFAULT_CONFIG;\n}\n\n// Initialize configuration on module load\ninitializeConfig();\n","import type { CreditSystemConfig } from \"./index.js\";\n\n/** Interface for external config providers (e.g. Firestore-backed) */\nexport interface ICreditConfigProvider {\n getConfig(): CreditSystemConfig;\n}\n\nlet configProvider: ICreditConfigProvider | null = null;\n\n/** Register an external config provider. Called once at app startup. */\nexport function registerConfigProvider(provider: ICreditConfigProvider): void {\n configProvider = provider;\n}\n\n/** Get the registered provider, or null if none registered. */\nexport function getConfigProvider(): ICreditConfigProvider | null {\n return configProvider;\n}\n\n/** Clear registered provider (for testing). */\nexport function clearConfigProvider(): void {\n configProvider = null;\n}\n","/**\n * Core credit operations - framework agnostic\n *\n * Contains the business logic for credit operations that can be\n * used by any adapter or service implementation.\n */\n\nimport type { ICreditRepository } from \"../repository/types.js\";\nimport type { PortableReservation } from \"./types.js\";\nimport { getOperationLabel } from \"../config/index.js\";\n\n/**\n * Commit a reservation with journal entry\n *\n * @param repository - The credit repository\n * @param userId - User ID\n * @param reservationId - Reservation to commit\n */\nexport async function commitReservationWithJournal(\n repository: ICreditRepository,\n userId: string,\n reservationId: string\n): Promise<void> {\n // Get the reservation to know the amount\n const reservation = await repository.getReservation(userId, reservationId);\n if (!reservation) {\n throw new Error(`Reservation ${reservationId} not found`);\n }\n\n // Commit the reservation atomically\n await repository.commitReservationAtomic(userId, reservationId);\n\n // Create journal entry\n const credits = await repository.getUserCredits(userId);\n if (credits) {\n await repository.createJournalEntry({\n userId,\n entryType: \"debit\",\n amount: reservation.amount,\n balanceAfter: credits.balance,\n source: \"operation_commit\",\n referenceId: reservationId,\n referenceType: \"reservation\",\n description: `Committed ${reservation.amount} credits for ${getOperationLabel(reservation.operationType)}`,\n metadata: {\n operationType: reservation.operationType,\n },\n });\n }\n}\n\n/**\n * Release a reservation with journal entry\n *\n * @param repository - The credit repository\n * @param userId - User ID\n * @param reservationId - Reservation to release\n */\nexport async function releaseReservationWithJournal(\n repository: ICreditRepository,\n userId: string,\n reservationId: string\n): Promise<void> {\n // Get the reservation to check its state\n const reservation = await repository.getReservation(userId, reservationId);\n\n // Release the reservation atomically\n await repository.releaseReservationAtomic(userId, reservationId);\n\n // Create journal entry only if reservation was in reserved state\n if (reservation?.status === \"reserved\") {\n const credits = await repository.getUserCredits(userId);\n if (credits) {\n await repository.createJournalEntry({\n userId,\n entryType: \"credit\",\n amount: 0, // No actual credits returned (they were reserved, not spent)\n balanceAfter: credits.balance,\n source: \"operation_release\",\n referenceId: reservationId,\n referenceType: \"reservation\",\n description: `Released ${reservation.amount} reserved credits for ${getOperationLabel(reservation.operationType)}`,\n metadata: {\n operationType: reservation.operationType,\n amount: reservation.amount,\n },\n });\n }\n }\n}\n\n/**\n * Reserve credits for an operation\n *\n * @param repository - The credit repository\n * @param userId - User ID\n * @param amount - Credits to reserve\n * @param operationType - Type of operation\n * @param expiryMs - Reservation expiry time in milliseconds\n * @returns The reservation\n */\nexport async function reserveCreditsForOperation(\n repository: ICreditRepository,\n userId: string,\n amount: number,\n operationType: string,\n expiryMs: number = 5 * 60 * 1000\n): Promise<PortableReservation> {\n const expiresAt = new Date(Date.now() + expiryMs);\n return repository.reserveCreditsAtomic(userId, amount, operationType, expiresAt);\n}\n","/**\n * Generic credits adapter - framework agnostic\n *\n * This adapter uses standard JavaScript APIs and works in any\n * JavaScript environment (Node.js, browser, etc.).\n */\n\nimport type { PortableReservation, WithCreditsOptions } from \"../core/types.js\";\nimport type { ActionResult, CreditsAdapter, CreditsAdapterConfig, CreditActionHandler } from \"./types.js\";\nimport {\n commitReservationWithJournal,\n releaseReservationWithJournal,\n reserveCreditsForOperation,\n} from \"../core/operations.js\";\n\n/**\n * Generate a unique request ID\n */\nfunction generateRequestId(): string {\n const timestamp = Date.now();\n const randomPart = Math.random().toString(36).substring(2, 14);\n return `req_${timestamp}_${randomPart}`;\n}\n\n/**\n * Create a generic credits adapter\n *\n * This adapter:\n * - Authenticates users via the provided auth provider\n * - Reserves credits before action execution\n * - Commits credits on success, releases on failure\n * - Logs usage asynchronously via the deferred executor\n */\nexport function createGenericAdapter(config: CreditsAdapterConfig): CreditsAdapter {\n const { repository, authProvider, deferred, operationCosts } = config;\n\n function getOperationCost(operationType: string): number {\n const cost = operationCosts[operationType];\n if (cost === undefined) {\n throw new Error(\n `Unknown operation type: \"${operationType}\". ` +\n `Configured types: ${Object.keys(operationCosts).join(\", \")}`\n );\n }\n return cost;\n }\n\n function logUsage(params: {\n userId: string;\n operationType: string;\n creditsUsed: number;\n success: boolean;\n errorMessage?: string;\n resourceId?: string;\n resourceType?: string;\n requestId: string;\n }): void {\n deferred.defer(async () => {\n await repository.logUsage({\n userId: params.userId,\n operationType: params.operationType,\n provider: \"gemini\",\n creditsUsed: params.creditsUsed,\n success: params.success,\n errorMessage: params.errorMessage,\n resourceId: params.resourceId,\n resourceType: params.resourceType,\n requestId: params.requestId,\n });\n });\n }\n\n return {\n withCredits<TInput, TOutput>(\n options: WithCreditsOptions,\n handler: CreditActionHandler<TInput, TOutput>\n ): (data: TInput) => Promise<ActionResult<TOutput>> {\n return async (data: TInput): Promise<ActionResult<TOutput>> => {\n const requestId = generateRequestId();\n\n // Authenticate\n const user = await authProvider.getCurrentUser();\n if (!user?.id) {\n return { success: false, error: \"Authentication required\" };\n }\n\n // Handle preview mode - skip credit handling entirely\n if (isPreviewMode(data)) {\n const dummyReservation = createDummyReservation(user.id, options.operationType);\n return handler(user, data, dummyReservation);\n }\n\n // Calculate cost and reserve credits\n let reservation: PortableReservation;\n try {\n const cost = options.customCost ?? getOperationCost(options.operationType);\n reservation = await reserveCreditsForOperation(\n repository,\n user.id,\n cost,\n options.operationType\n );\n } catch (error) {\n const message = error instanceof Error ? error.message : \"Failed to reserve credits\";\n logUsage({\n userId: user.id,\n operationType: options.operationType,\n creditsUsed: 0,\n success: false,\n errorMessage: message,\n resourceId: options.resourceId,\n resourceType: options.resourceType,\n requestId,\n });\n return { success: false, error: message };\n }\n\n // Execute action\n try {\n const result = await handler(user, data, reservation);\n\n if (result.success) {\n await commitReservationWithJournal(repository, user.id, reservation.id);\n logUsage({\n userId: user.id,\n operationType: options.operationType,\n creditsUsed: reservation.amount,\n success: true,\n resourceId: options.resourceId,\n resourceType: options.resourceType,\n requestId,\n });\n } else {\n await releaseReservationWithJournal(repository, user.id, reservation.id);\n logUsage({\n userId: user.id,\n operationType: options.operationType,\n creditsUsed: 0,\n success: false,\n errorMessage: result.error,\n resourceId: options.resourceId,\n resourceType: options.resourceType,\n requestId,\n });\n }\n\n return result;\n } catch (error) {\n const message = error instanceof Error ? error.message : \"An unexpected error occurred\";\n await releaseReservationWithJournal(repository, user.id, reservation.id);\n logUsage({\n userId: user.id,\n operationType: options.operationType,\n creditsUsed: 0,\n success: false,\n errorMessage: message,\n resourceId: options.resourceId,\n resourceType: options.resourceType,\n requestId,\n });\n console.error(\"Action error:\", error);\n return { success: false, error: message };\n }\n };\n },\n };\n}\n\nfunction isPreviewMode(data: unknown): boolean {\n return (\n typeof data === \"object\" &&\n data !== null &&\n \"preview\" in data &&\n (data as Record<string, unknown>).preview === true\n );\n}\n\nfunction createDummyReservation(userId: string, operationType: string): PortableReservation {\n return {\n id: \"preview-mode\",\n userId,\n amount: 0,\n operationType,\n status: \"released\",\n createdAt: new Date().toISOString(),\n expiresAt: new Date().toISOString(),\n };\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/config/index.ts","../../src/config/provider.ts","../../src/core/operations.ts","../../src/adapters/generic.ts"],"sourcesContent":["import { z } from \"zod\";\nimport type { SubscriptionTier, TierConfig } from \"../core/types.js\";\nimport { getConfigProvider } from \"./provider.js\";\n\n/**\n * Schema for tier configuration\n */\nconst tierConfigSchema = z.object({\n tier: z.string().min(1),\n monthlyCredits: z.number().min(0),\n priceUsd: z.number().min(0),\n features: z.array(z.string()),\n isFree: z.boolean().optional(),\n unlimited: z.boolean().optional(),\n isDefault: z.boolean().optional(),\n});\n\n/**\n * Schema for credit system configuration\n */\nconst creditSystemConfigSchema = z.object({\n /**\n * Operation costs in credits\n * Keys are operation type names (any non-empty string)\n * Values are positive numbers representing credit cost\n */\n operationCosts: z.record(\n z.string().min(1),\n z.number().positive()\n ),\n\n /**\n * Display labels for operation types (English canonical labels).\n * Falls back to auto-generating from snake_case keys if not provided.\n */\n operationLabels: z.record(\n z.string().min(1),\n z.string().min(1)\n ).optional().default({}),\n\n /**\n * Tier configurations\n */\n tierConfigs: z.record(\n z.string().min(1),\n tierConfigSchema\n ),\n\n /**\n * Reservation expiry time in milliseconds\n */\n reservationExpiryMs: z.number().positive().default(5 * 60 * 1000),\n\n /**\n * Default credits for new users (free tier)\n */\n defaultFreeCredits: z.number().positive().default(25),\n\n /**\n * Grace period in days after subscription expires before downgrade\n */\n subscriptionGracePeriodDays: z.number().min(0).default(3),\n\n /**\n * Low balance notification threshold\n */\n lowBalanceThreshold: z.number().min(0).default(10),\n\n /**\n * Cooldown in hours between low balance notifications\n */\n lowBalanceNotificationCooldownHours: z.number().positive().default(24),\n\n /**\n * Feature flags for optional features\n */\n features: z.object({\n journalEntries: z.boolean().default(true),\n notifications: z.boolean().default(true),\n subscriptionExpiry: z.boolean().default(true),\n usageHistory: z.boolean().default(true),\n }).default({\n journalEntries: true,\n notifications: true,\n subscriptionExpiry: true,\n usageHistory: true,\n }),\n});\n\n/**\n * Credit system configuration type\n */\nexport type CreditSystemConfig = z.infer<typeof creditSystemConfigSchema>;\n\n/**\n * Default configuration\n */\nconst DEFAULT_CONFIG: CreditSystemConfig = {\n operationCosts: {\n story_generation: 5,\n conversation: 2,\n image_generation: 10,\n template_generation: 10,\n },\n operationLabels: {\n story_generation: \"Story generation\",\n conversation: \"Conversation\",\n image_generation: \"Image generation\",\n template_generation: \"Template generation\",\n },\n tierConfigs: {\n free: {\n tier: \"free\",\n monthlyCredits: 25,\n priceUsd: 0,\n isFree: true,\n isDefault: true,\n features: [\n \"25 credits per month\",\n \"Basic story generation\",\n \"Standard image quality\",\n ],\n },\n basic: {\n tier: \"basic\",\n monthlyCredits: 200,\n priceUsd: 9.99,\n features: [\n \"200 credits per month\",\n \"All story features\",\n \"High quality images\",\n \"Priority support\",\n ],\n },\n premium: {\n tier: \"premium\",\n monthlyCredits: 500,\n priceUsd: 19.99,\n features: [\n \"500 credits per month\",\n \"All features\",\n \"Highest quality images\",\n \"Early access to new features\",\n \"Priority support\",\n ],\n },\n unlimited: {\n tier: \"unlimited\",\n monthlyCredits: 0, // 0 = unlimited\n unlimited: true,\n priceUsd: 49.99,\n features: [\n \"Unlimited credits\",\n \"All features\",\n \"Highest quality images\",\n \"Early access to new features\",\n \"Dedicated support\",\n ],\n },\n },\n reservationExpiryMs: 5 * 60 * 1000, // 5 minutes\n defaultFreeCredits: 25,\n subscriptionGracePeriodDays: 3,\n lowBalanceThreshold: 10,\n lowBalanceNotificationCooldownHours: 24,\n features: {\n journalEntries: true,\n notifications: true,\n subscriptionExpiry: true,\n usageHistory: true,\n },\n};\n\n/**\n * Current configuration (can be overridden)\n */\nlet currentConfig: CreditSystemConfig = DEFAULT_CONFIG;\n\n/**\n * Load configuration from environment variables\n * Can be extended to load from other sources (file, remote config, etc.)\n *\n * Operation costs are loaded from CREDITS_OPERATION_COSTS as JSON:\n * CREDITS_OPERATION_COSTS={\"story_generation\":5,\"image_generation\":10}\n */\nexport function loadConfigFromEnv(): Partial<CreditSystemConfig> {\n const envConfig: Partial<CreditSystemConfig> = {};\n\n // Load operation costs from env as JSON\n const operationCostsEnv = process.env.CREDITS_OPERATION_COSTS;\n if (operationCostsEnv) {\n try {\n const parsed = JSON.parse(operationCostsEnv);\n if (typeof parsed === \"object\" && parsed !== null) {\n envConfig.operationCosts = {\n ...DEFAULT_CONFIG.operationCosts,\n ...parsed,\n };\n }\n } catch {\n console.warn(\"[Credits Config] Failed to parse CREDITS_OPERATION_COSTS env var as JSON\");\n }\n }\n\n // Load other settings from env\n const reservationExpiry = process.env.CREDITS_RESERVATION_EXPIRY_MS;\n if (reservationExpiry) {\n envConfig.reservationExpiryMs = parseInt(reservationExpiry, 10);\n }\n\n const defaultCredits = process.env.CREDITS_DEFAULT_FREE;\n if (defaultCredits) {\n envConfig.defaultFreeCredits = parseInt(defaultCredits, 10);\n }\n\n const gracePeriod = process.env.CREDITS_GRACE_PERIOD_DAYS;\n if (gracePeriod) {\n envConfig.subscriptionGracePeriodDays = parseInt(gracePeriod, 10);\n }\n\n const lowBalanceThreshold = process.env.CREDITS_LOW_BALANCE_THRESHOLD;\n if (lowBalanceThreshold) {\n envConfig.lowBalanceThreshold = parseInt(lowBalanceThreshold, 10);\n }\n\n // Load feature flags\n const featuresEnv = process.env.CREDITS_FEATURES;\n if (featuresEnv) {\n try {\n const features = JSON.parse(featuresEnv);\n envConfig.features = {\n ...DEFAULT_CONFIG.features,\n ...features,\n };\n } catch {\n console.warn(\"[Credits Config] Failed to parse CREDITS_FEATURES env var\");\n }\n }\n\n return envConfig;\n}\n\n/**\n * Initialize configuration\n * Merges default config with environment overrides\n */\nexport function initializeConfig(overrides?: Partial<CreditSystemConfig>): CreditSystemConfig {\n const envConfig = loadConfigFromEnv();\n\n const mergedConfig = {\n ...DEFAULT_CONFIG,\n ...envConfig,\n ...overrides,\n operationCosts: {\n ...DEFAULT_CONFIG.operationCosts,\n ...envConfig.operationCosts,\n ...overrides?.operationCosts,\n },\n operationLabels: {\n ...DEFAULT_CONFIG.operationLabels,\n ...envConfig.operationLabels,\n ...overrides?.operationLabels,\n },\n tierConfigs: {\n ...DEFAULT_CONFIG.tierConfigs,\n ...envConfig.tierConfigs,\n ...overrides?.tierConfigs,\n },\n features: {\n ...DEFAULT_CONFIG.features,\n ...envConfig.features,\n ...overrides?.features,\n },\n };\n\n // Validate configuration\n const result = creditSystemConfigSchema.safeParse(mergedConfig);\n if (!result.success) {\n console.error(\"[Credits Config] Invalid configuration:\", result.error.issues);\n throw new Error(\"Invalid credit system configuration\");\n }\n\n currentConfig = result.data;\n return currentConfig;\n}\n\n/**\n * Get the current configuration.\n * If an external provider is registered, delegates to it.\n * Otherwise falls back to the local currentConfig.\n */\nexport function getConfig(): CreditSystemConfig {\n const provider = getConfigProvider();\n if (provider) {\n return provider.getConfig();\n }\n return currentConfig;\n}\n\n/**\n * Get all valid operation types from configuration\n * Returns the keys of the operationCosts object\n */\nexport function getValidOperationTypes(): string[] {\n return Object.keys(getConfig().operationCosts);\n}\n\n/**\n * Check if an operation type is valid (configured in the system)\n * @param type - The operation type to check\n * @returns true if the operation type is configured\n */\nexport function isValidOperationType(type: string): boolean {\n return type in getConfig().operationCosts;\n}\n\n/**\n * Get operation cost from configuration\n * @throws Error if the operation type is not configured\n */\nexport function getConfigOperationCost(operationType: string): number {\n const cost = getConfig().operationCosts[operationType];\n if (cost === undefined) {\n throw new Error(\n `Unknown operation type: ${operationType}. Valid types: ${getValidOperationTypes().join(\", \")}`\n );\n }\n return cost;\n}\n\n/**\n * Get all valid tier ids from configuration\n * Returns the keys of the tierConfigs object\n */\nexport function getValidTiers(): string[] {\n return Object.keys(getConfig().tierConfigs);\n}\n\n/**\n * Check if a tier id is valid (configured in the system)\n */\nexport function isValidTier(tier: string): boolean {\n return tier in getConfig().tierConfigs;\n}\n\n/**\n * Check if a tier is the free/default tier.\n * Falls back to priceUsd === 0 when the isFree flag is not set.\n */\nexport function isFreeTier(tier: SubscriptionTier): boolean {\n const c = getConfig().tierConfigs[tier];\n if (!c) return false;\n return c.isFree ?? c.priceUsd === 0;\n}\n\n/**\n * Check if a tier is unlimited.\n * Falls back to monthlyCredits === 0 when the unlimited flag is not set.\n */\nexport function isUnlimitedTier(tier: SubscriptionTier): boolean {\n const c = getConfig().tierConfigs[tier];\n if (!c) return false;\n return c.unlimited ?? c.monthlyCredits === 0;\n}\n\n/**\n * Get the tier assigned to brand-new users.\n * Prefers the tier flagged isDefault, then the first isFree tier, then \"free\".\n */\nexport function getDefaultTier(): SubscriptionTier {\n const entries = Object.entries(getConfig().tierConfigs);\n const def =\n entries.find(([, c]) => c.isDefault)?.[0] ??\n entries.find(([, c]) => c.isFree ?? c.priceUsd === 0)?.[0];\n return def ?? \"free\";\n}\n\n/**\n * Magic balance assigned to unlimited tiers on upgrade.\n * Kept at 999999 for back-compat with stored/displayed balances.\n */\nexport const UNLIMITED_BALANCE_SENTINEL = 999999;\n\n/**\n * Get the magic balance assigned to unlimited tiers on upgrade.\n */\nexport function getUnlimitedSentinelBalance(): number {\n return UNLIMITED_BALANCE_SENTINEL;\n}\n\n/**\n * Get tier configuration from configuration.\n * Falls back to the default tier (with a warning) if the tier is unknown.\n */\nexport function getConfigTierConfig(tier: SubscriptionTier): TierConfig {\n const cfg = getConfig().tierConfigs[tier];\n if (cfg) return cfg;\n const fallback = getDefaultTier();\n console.warn(\n `[Credits Config] Unknown tier \"${tier}\". Falling back to \"${fallback}\". Valid: ${getValidTiers().join(\", \")}`\n );\n return getConfig().tierConfigs[fallback]!;\n}\n\n/**\n * Get monthly limit for a tier from configuration\n * Returns Infinity for unlimited tier\n */\nexport function getConfigMonthlyLimit(tier: SubscriptionTier): number {\n return isUnlimitedTier(tier) ? Infinity : getConfigTierConfig(tier).monthlyCredits;\n}\n\n/**\n * Runtime zod validator for tier ids. Validates against the LIVE config keys\n * at parse time, so apps that add tiers via config get correct validation.\n */\nexport const tierSchema = z\n .string()\n .refine(isValidTier, { message: \"Unknown subscription tier\" }) as unknown as z.ZodType<SubscriptionTier>;\n\n/**\n * Parse/validate a tier id against the live config keys.\n * @throws ZodError if the tier is not configured\n */\nexport function parseTier(value: unknown): SubscriptionTier {\n return tierSchema.parse(value);\n}\n\n/**\n * Check if a feature is enabled\n */\nexport function isFeatureEnabled(feature: keyof CreditSystemConfig[\"features\"]): boolean {\n return getConfig().features[feature];\n}\n\n/**\n * Convert a snake_case string to Title Case.\n * E.g., \"story_generation\" -> \"Story Generation\"\n */\nfunction snakeCaseToTitleCase(str: string): string {\n return str\n .replace(/_/g, \" \")\n .replace(/\\b\\w/g, (c) => c.toUpperCase());\n}\n\n/**\n * Get the display label for an operation type.\n * Falls back to converting snake_case to Title Case if no label is configured.\n */\nexport function getOperationLabel(operationType: string): string {\n const labels = getConfig().operationLabels;\n if (labels && operationType in labels) {\n return labels[operationType];\n }\n return snakeCaseToTitleCase(operationType);\n}\n\n/**\n * Get all operation labels as a record.\n * For any operation in operationCosts that lacks a label, generates one from the key.\n */\nexport function getOperationLabels(): Record<string, string> {\n const config = getConfig();\n const result: Record<string, string> = {};\n for (const key of Object.keys(config.operationCosts)) {\n result[key] = config.operationLabels?.[key]\n ?? snakeCaseToTitleCase(key);\n }\n return result;\n}\n\n/**\n * Reset configuration to defaults (for testing)\n */\nexport function resetConfig(): void {\n currentConfig = DEFAULT_CONFIG;\n}\n\n// Initialize configuration on module load\ninitializeConfig();\n","import type { CreditSystemConfig } from \"./index.js\";\n\n/** Interface for external config providers (e.g. Firestore-backed) */\nexport interface ICreditConfigProvider {\n getConfig(): CreditSystemConfig;\n}\n\nlet configProvider: ICreditConfigProvider | null = null;\n\n/** Register an external config provider. Called once at app startup. */\nexport function registerConfigProvider(provider: ICreditConfigProvider): void {\n configProvider = provider;\n}\n\n/** Get the registered provider, or null if none registered. */\nexport function getConfigProvider(): ICreditConfigProvider | null {\n return configProvider;\n}\n\n/** Clear registered provider (for testing). */\nexport function clearConfigProvider(): void {\n configProvider = null;\n}\n","/**\n * Core credit operations - framework agnostic\n *\n * Contains the business logic for credit operations that can be\n * used by any adapter or service implementation.\n */\n\nimport type { ICreditRepository } from \"../repository/types.js\";\nimport type { PortableReservation } from \"./types.js\";\nimport { getOperationLabel } from \"../config/index.js\";\n\n/**\n * Commit a reservation with journal entry\n *\n * @param repository - The credit repository\n * @param userId - User ID\n * @param reservationId - Reservation to commit\n */\nexport async function commitReservationWithJournal(\n repository: ICreditRepository,\n userId: string,\n reservationId: string\n): Promise<void> {\n // Get the reservation to know the amount\n const reservation = await repository.getReservation(userId, reservationId);\n if (!reservation) {\n throw new Error(`Reservation ${reservationId} not found`);\n }\n\n // Commit the reservation atomically\n await repository.commitReservationAtomic(userId, reservationId);\n\n // Create journal entry\n const credits = await repository.getUserCredits(userId);\n if (credits) {\n await repository.createJournalEntry({\n userId,\n entryType: \"debit\",\n amount: reservation.amount,\n balanceAfter: credits.balance,\n source: \"operation_commit\",\n referenceId: reservationId,\n referenceType: \"reservation\",\n description: `Committed ${reservation.amount} credits for ${getOperationLabel(reservation.operationType)}`,\n metadata: {\n operationType: reservation.operationType,\n },\n });\n }\n}\n\n/**\n * Release a reservation with journal entry\n *\n * @param repository - The credit repository\n * @param userId - User ID\n * @param reservationId - Reservation to release\n */\nexport async function releaseReservationWithJournal(\n repository: ICreditRepository,\n userId: string,\n reservationId: string\n): Promise<void> {\n // Get the reservation to check its state\n const reservation = await repository.getReservation(userId, reservationId);\n\n // Release the reservation atomically\n await repository.releaseReservationAtomic(userId, reservationId);\n\n // Create journal entry only if reservation was in reserved state\n if (reservation?.status === \"reserved\") {\n const credits = await repository.getUserCredits(userId);\n if (credits) {\n await repository.createJournalEntry({\n userId,\n entryType: \"credit\",\n amount: 0, // No actual credits returned (they were reserved, not spent)\n balanceAfter: credits.balance,\n source: \"operation_release\",\n referenceId: reservationId,\n referenceType: \"reservation\",\n description: `Released ${reservation.amount} reserved credits for ${getOperationLabel(reservation.operationType)}`,\n metadata: {\n operationType: reservation.operationType,\n amount: reservation.amount,\n },\n });\n }\n }\n}\n\n/**\n * Reserve credits for an operation\n *\n * @param repository - The credit repository\n * @param userId - User ID\n * @param amount - Credits to reserve\n * @param operationType - Type of operation\n * @param expiryMs - Reservation expiry time in milliseconds\n * @returns The reservation\n */\nexport async function reserveCreditsForOperation(\n repository: ICreditRepository,\n userId: string,\n amount: number,\n operationType: string,\n expiryMs: number = 5 * 60 * 1000\n): Promise<PortableReservation> {\n const expiresAt = new Date(Date.now() + expiryMs);\n return repository.reserveCreditsAtomic(userId, amount, operationType, expiresAt);\n}\n","/**\n * Generic credits adapter - framework agnostic\n *\n * This adapter uses standard JavaScript APIs and works in any\n * JavaScript environment (Node.js, browser, etc.).\n */\n\nimport type { PortableReservation, WithCreditsOptions } from \"../core/types.js\";\nimport type { ActionResult, CreditsAdapter, CreditsAdapterConfig, CreditActionHandler } from \"./types.js\";\nimport {\n commitReservationWithJournal,\n releaseReservationWithJournal,\n reserveCreditsForOperation,\n} from \"../core/operations.js\";\n\n/**\n * Generate a unique request ID\n */\nfunction generateRequestId(): string {\n const timestamp = Date.now();\n const randomPart = Math.random().toString(36).substring(2, 14);\n return `req_${timestamp}_${randomPart}`;\n}\n\n/**\n * Create a generic credits adapter\n *\n * This adapter:\n * - Authenticates users via the provided auth provider\n * - Reserves credits before action execution\n * - Commits credits on success, releases on failure\n * - Logs usage asynchronously via the deferred executor\n */\nexport function createGenericAdapter(config: CreditsAdapterConfig): CreditsAdapter {\n const { repository, authProvider, deferred, operationCosts } = config;\n\n function getOperationCost(operationType: string): number {\n const cost = operationCosts[operationType];\n if (cost === undefined) {\n throw new Error(\n `Unknown operation type: \"${operationType}\". ` +\n `Configured types: ${Object.keys(operationCosts).join(\", \")}`\n );\n }\n return cost;\n }\n\n function logUsage(params: {\n userId: string;\n operationType: string;\n creditsUsed: number;\n success: boolean;\n errorMessage?: string;\n resourceId?: string;\n resourceType?: string;\n requestId: string;\n }): void {\n deferred.defer(async () => {\n await repository.logUsage({\n userId: params.userId,\n operationType: params.operationType,\n provider: \"gemini\",\n creditsUsed: params.creditsUsed,\n success: params.success,\n errorMessage: params.errorMessage,\n resourceId: params.resourceId,\n resourceType: params.resourceType,\n requestId: params.requestId,\n });\n });\n }\n\n return {\n withCredits<TInput, TOutput>(\n options: WithCreditsOptions,\n handler: CreditActionHandler<TInput, TOutput>\n ): (data: TInput) => Promise<ActionResult<TOutput>> {\n return async (data: TInput): Promise<ActionResult<TOutput>> => {\n const requestId = generateRequestId();\n\n // Authenticate\n const user = await authProvider.getCurrentUser();\n if (!user?.id) {\n return { success: false, error: \"Authentication required\" };\n }\n\n // Handle preview mode - skip credit handling entirely\n if (isPreviewMode(data)) {\n const dummyReservation = createDummyReservation(user.id, options.operationType);\n return handler(user, data, dummyReservation);\n }\n\n // Calculate cost and reserve credits\n let reservation: PortableReservation;\n try {\n const cost = options.customCost ?? getOperationCost(options.operationType);\n reservation = await reserveCreditsForOperation(\n repository,\n user.id,\n cost,\n options.operationType\n );\n } catch (error) {\n const message = error instanceof Error ? error.message : \"Failed to reserve credits\";\n logUsage({\n userId: user.id,\n operationType: options.operationType,\n creditsUsed: 0,\n success: false,\n errorMessage: message,\n resourceId: options.resourceId,\n resourceType: options.resourceType,\n requestId,\n });\n return { success: false, error: message };\n }\n\n // Execute action\n try {\n const result = await handler(user, data, reservation);\n\n if (result.success) {\n await commitReservationWithJournal(repository, user.id, reservation.id);\n logUsage({\n userId: user.id,\n operationType: options.operationType,\n creditsUsed: reservation.amount,\n success: true,\n resourceId: options.resourceId,\n resourceType: options.resourceType,\n requestId,\n });\n } else {\n await releaseReservationWithJournal(repository, user.id, reservation.id);\n logUsage({\n userId: user.id,\n operationType: options.operationType,\n creditsUsed: 0,\n success: false,\n errorMessage: result.error,\n resourceId: options.resourceId,\n resourceType: options.resourceType,\n requestId,\n });\n }\n\n return result;\n } catch (error) {\n const message = error instanceof Error ? error.message : \"An unexpected error occurred\";\n await releaseReservationWithJournal(repository, user.id, reservation.id);\n logUsage({\n userId: user.id,\n operationType: options.operationType,\n creditsUsed: 0,\n success: false,\n errorMessage: message,\n resourceId: options.resourceId,\n resourceType: options.resourceType,\n requestId,\n });\n console.error(\"Action error:\", error);\n return { success: false, error: message };\n }\n };\n },\n };\n}\n\nfunction isPreviewMode(data: unknown): boolean {\n return (\n typeof data === \"object\" &&\n data !== null &&\n \"preview\" in data &&\n (data as Record<string, unknown>).preview === true\n );\n}\n\nfunction createDummyReservation(userId: string, operationType: string): PortableReservation {\n return {\n id: \"preview-mode\",\n userId,\n amount: 0,\n operationType,\n status: \"released\",\n createdAt: new Date().toISOString(),\n expiresAt: new Date().toISOString(),\n };\n}\n"],"mappings":"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","names":[]}
|
package/dist/core/index.cjs
CHANGED
|
@@ -204,10 +204,13 @@ function getConfigProvider() {
|
|
|
204
204
|
|
|
205
205
|
// src/config/index.ts
|
|
206
206
|
var tierConfigSchema = import_zod.z.object({
|
|
207
|
-
tier: import_zod.z.
|
|
207
|
+
tier: import_zod.z.string().min(1),
|
|
208
208
|
monthlyCredits: import_zod.z.number().min(0),
|
|
209
209
|
priceUsd: import_zod.z.number().min(0),
|
|
210
|
-
features: import_zod.z.array(import_zod.z.string())
|
|
210
|
+
features: import_zod.z.array(import_zod.z.string()),
|
|
211
|
+
isFree: import_zod.z.boolean().optional(),
|
|
212
|
+
unlimited: import_zod.z.boolean().optional(),
|
|
213
|
+
isDefault: import_zod.z.boolean().optional()
|
|
211
214
|
});
|
|
212
215
|
var creditSystemConfigSchema = import_zod.z.object({
|
|
213
216
|
/**
|
|
@@ -231,7 +234,7 @@ var creditSystemConfigSchema = import_zod.z.object({
|
|
|
231
234
|
* Tier configurations
|
|
232
235
|
*/
|
|
233
236
|
tierConfigs: import_zod.z.record(
|
|
234
|
-
import_zod.z.
|
|
237
|
+
import_zod.z.string().min(1),
|
|
235
238
|
tierConfigSchema
|
|
236
239
|
),
|
|
237
240
|
/**
|
|
@@ -287,6 +290,8 @@ var DEFAULT_CONFIG = {
|
|
|
287
290
|
tier: "free",
|
|
288
291
|
monthlyCredits: 25,
|
|
289
292
|
priceUsd: 0,
|
|
293
|
+
isFree: true,
|
|
294
|
+
isDefault: true,
|
|
290
295
|
features: [
|
|
291
296
|
"25 credits per month",
|
|
292
297
|
"Basic story generation",
|
|
@@ -320,6 +325,7 @@ var DEFAULT_CONFIG = {
|
|
|
320
325
|
tier: "unlimited",
|
|
321
326
|
monthlyCredits: 0,
|
|
322
327
|
// 0 = unlimited
|
|
328
|
+
unlimited: true,
|
|
323
329
|
priceUsd: 49.99,
|
|
324
330
|
features: [
|
|
325
331
|
"Unlimited credits",
|
|
@@ -432,6 +438,10 @@ function getConfig() {
|
|
|
432
438
|
}
|
|
433
439
|
return currentConfig;
|
|
434
440
|
}
|
|
441
|
+
function isValidTier(tier) {
|
|
442
|
+
return tier in getConfig().tierConfigs;
|
|
443
|
+
}
|
|
444
|
+
var tierSchema = import_zod.z.string().refine(isValidTier, { message: "Unknown subscription tier" });
|
|
435
445
|
function snakeCaseToTitleCase(str) {
|
|
436
446
|
return str.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
437
447
|
}
|