@nehorai/credits 1.2.0 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/adapters/index.cjs +463 -0
- package/dist/adapters/index.cjs.map +1 -0
- package/dist/adapters/index.d.cts +66 -0
- package/dist/adapters/index.d.ts +66 -3
- package/dist/adapters/index.js +435 -1
- package/dist/adapters/index.js.map +1 -1
- package/dist/auth/index.cjs +96 -0
- package/dist/auth/index.cjs.map +1 -0
- package/dist/auth/{api-key.d.ts → index.d.cts} +9 -6
- package/dist/auth/index.d.ts +37 -5
- package/dist/auth/index.js +65 -7
- package/dist/auth/index.js.map +1 -1
- package/dist/core/index.cjs +522 -0
- package/dist/core/index.cjs.map +1 -0
- package/dist/core/index.d.cts +119 -0
- package/dist/core/index.d.ts +115 -25
- package/dist/core/index.js +475 -27
- package/dist/core/index.js.map +1 -1
- package/dist/{core/deferred.d.ts → deferred-C7rrRfkm.d.cts} +7 -6
- package/dist/{core/deferred.js → deferred-C7rrRfkm.d.ts} +23 -29
- package/dist/index.cjs +2514 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +437 -0
- package/dist/index.d.ts +429 -50
- package/dist/index.js +2407 -63
- package/dist/index.js.map +1 -1
- package/dist/repository/index.cjs +819 -0
- package/dist/repository/index.cjs.map +1 -0
- package/dist/repository/{memory/index.d.ts → index.d.cts} +35 -5
- package/dist/repository/index.d.ts +97 -7
- package/dist/repository/index.js +786 -10
- package/dist/repository/index.js.map +1 -1
- package/dist/sdk/index.cjs +608 -0
- package/dist/sdk/index.cjs.map +1 -0
- package/dist/sdk/index.d.cts +495 -0
- package/dist/sdk/index.d.ts +491 -9
- package/dist/sdk/index.js +568 -12
- package/dist/sdk/index.js.map +1 -1
- package/dist/service/index.cjs +675 -0
- package/dist/service/index.cjs.map +1 -0
- package/dist/service/{credits-service.d.ts → index.d.cts} +25 -8
- package/dist/service/index.d.ts +156 -3
- package/dist/service/index.js +646 -1
- package/dist/service/index.js.map +1 -1
- package/dist/{core/types.d.ts → types-BBxIWHK2.d.cts} +25 -24
- package/dist/types-BBxIWHK2.d.ts +277 -0
- package/dist/types-D1vdeBcA.d.cts +305 -0
- package/dist/{auth/types.d.ts → types-OZU3I5OM.d.cts} +5 -4
- package/dist/types-OZU3I5OM.d.ts +32 -0
- package/dist/{repository/types.d.ts → types-aJsyv8df.d.ts} +35 -13
- package/package.json +21 -19
- package/dist/adapters/generic.d.ts +0 -18
- package/dist/adapters/generic.d.ts.map +0 -1
- package/dist/adapters/generic.js +0 -151
- package/dist/adapters/generic.js.map +0 -1
- package/dist/adapters/index.d.ts.map +0 -1
- package/dist/adapters/types.d.ts +0 -45
- package/dist/adapters/types.d.ts.map +0 -1
- package/dist/adapters/types.js +0 -8
- package/dist/adapters/types.js.map +0 -1
- package/dist/auth/api-key.d.ts.map +0 -1
- package/dist/auth/api-key.js +0 -94
- package/dist/auth/api-key.js.map +0 -1
- package/dist/auth/index.d.ts.map +0 -1
- package/dist/auth/types.d.ts.map +0 -1
- package/dist/auth/types.js +0 -2
- package/dist/auth/types.js.map +0 -1
- package/dist/config/costs.d.ts +0 -56
- package/dist/config/costs.d.ts.map +0 -1
- package/dist/config/costs.js +0 -71
- package/dist/config/costs.js.map +0 -1
- package/dist/config/index.d.ts +0 -184
- package/dist/config/index.d.ts.map +0 -1
- package/dist/config/index.js +0 -342
- package/dist/config/index.js.map +0 -1
- package/dist/config/provider.d.ts +0 -12
- package/dist/config/provider.d.ts.map +0 -1
- package/dist/config/provider.js +0 -14
- package/dist/config/provider.js.map +0 -1
- package/dist/core/deferred.d.ts.map +0 -1
- package/dist/core/deferred.js.map +0 -1
- package/dist/core/errors.d.ts +0 -78
- package/dist/core/errors.d.ts.map +0 -1
- package/dist/core/errors.js +0 -110
- package/dist/core/errors.js.map +0 -1
- package/dist/core/index.d.ts.map +0 -1
- package/dist/core/operations.d.ts +0 -36
- package/dist/core/operations.d.ts.map +0 -1
- package/dist/core/operations.js +0 -88
- package/dist/core/operations.js.map +0 -1
- package/dist/core/types.d.ts.map +0 -1
- package/dist/core/types.js +0 -82
- package/dist/core/types.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/notifications/index.d.ts +0 -76
- package/dist/notifications/index.d.ts.map +0 -1
- package/dist/notifications/index.js +0 -174
- package/dist/notifications/index.js.map +0 -1
- package/dist/repository/index.d.ts.map +0 -1
- package/dist/repository/memory/index.d.ts.map +0 -1
- package/dist/repository/memory/index.js +0 -518
- package/dist/repository/memory/index.js.map +0 -1
- package/dist/repository/types.d.ts.map +0 -1
- package/dist/repository/types.js +0 -39
- package/dist/repository/types.js.map +0 -1
- package/dist/repository/utils.d.ts +0 -22
- package/dist/repository/utils.d.ts.map +0 -1
- package/dist/repository/utils.js +0 -40
- package/dist/repository/utils.js.map +0 -1
- package/dist/sdk/admin-client.d.ts +0 -146
- package/dist/sdk/admin-client.d.ts.map +0 -1
- package/dist/sdk/admin-client.js +0 -196
- package/dist/sdk/admin-client.js.map +0 -1
- package/dist/sdk/client.d.ts +0 -144
- package/dist/sdk/client.d.ts.map +0 -1
- package/dist/sdk/client.js +0 -247
- package/dist/sdk/client.js.map +0 -1
- package/dist/sdk/errors.d.ts +0 -91
- package/dist/sdk/errors.d.ts.map +0 -1
- package/dist/sdk/errors.js +0 -185
- package/dist/sdk/errors.js.map +0 -1
- package/dist/sdk/index.d.ts.map +0 -1
- package/dist/sdk/types.d.ts +0 -110
- package/dist/sdk/types.d.ts.map +0 -1
- package/dist/sdk/types.js +0 -20
- package/dist/sdk/types.js.map +0 -1
- package/dist/service/credits-service.d.ts.map +0 -1
- package/dist/service/credits-service.js +0 -368
- package/dist/service/credits-service.js.map +0 -1
- package/dist/service/index.d.ts.map +0 -1
- package/dist/utils/error-utils.d.ts +0 -48
- package/dist/utils/error-utils.d.ts.map +0 -1
- package/dist/utils/error-utils.js +0 -57
- package/dist/utils/error-utils.js.map +0 -1
- package/dist/utils/index.d.ts +0 -54
- package/dist/utils/index.d.ts.map +0 -1
- package/dist/utils/index.js +0 -72
- package/dist/utils/index.js.map +0 -1
|
@@ -0,0 +1,819 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/repository/index.ts
|
|
21
|
+
var repository_exports = {};
|
|
22
|
+
__export(repository_exports, {
|
|
23
|
+
InMemoryCreditRepository: () => InMemoryCreditRepository,
|
|
24
|
+
createInMemoryCreditRepository: () => createInMemoryCreditRepository,
|
|
25
|
+
generateId: () => generateId,
|
|
26
|
+
getNextMonthlyReset: () => getNextMonthlyReset,
|
|
27
|
+
toClientUserCredits: () => toClientUserCredits,
|
|
28
|
+
toDate: () => toDate
|
|
29
|
+
});
|
|
30
|
+
module.exports = __toCommonJS(repository_exports);
|
|
31
|
+
|
|
32
|
+
// src/repository/types.ts
|
|
33
|
+
function toClientUserCredits(credits) {
|
|
34
|
+
return {
|
|
35
|
+
userId: credits.userId,
|
|
36
|
+
balance: credits.balance,
|
|
37
|
+
bonusCredits: credits.bonusCredits ?? 0,
|
|
38
|
+
reserved: credits.reserved,
|
|
39
|
+
tier: credits.tier,
|
|
40
|
+
monthlyLimit: credits.monthlyLimit,
|
|
41
|
+
monthlyUsed: credits.monthlyUsed,
|
|
42
|
+
monthlyResetAt: toISOString(credits.monthlyResetAt),
|
|
43
|
+
subscriptionExpiresAt: credits.subscriptionExpiresAt ? toISOString(credits.subscriptionExpiresAt) : null,
|
|
44
|
+
createdAt: toISOString(credits.createdAt),
|
|
45
|
+
updatedAt: toISOString(credits.updatedAt)
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
function toISOString(value) {
|
|
49
|
+
if (!value) return (/* @__PURE__ */ new Date()).toISOString();
|
|
50
|
+
if (typeof value === "string") return value;
|
|
51
|
+
if (value instanceof Date) return value.toISOString();
|
|
52
|
+
if (typeof value === "object" && "toDate" in value && typeof value.toDate === "function") {
|
|
53
|
+
return value.toDate().toISOString();
|
|
54
|
+
}
|
|
55
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// src/repository/utils.ts
|
|
59
|
+
function generateId() {
|
|
60
|
+
return `${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
|
|
61
|
+
}
|
|
62
|
+
function toDate(value) {
|
|
63
|
+
if (value instanceof Date) return value;
|
|
64
|
+
if (typeof value === "string") return new Date(value);
|
|
65
|
+
if (typeof value === "object" && value !== null && "toDate" in value) {
|
|
66
|
+
return value.toDate();
|
|
67
|
+
}
|
|
68
|
+
return /* @__PURE__ */ new Date();
|
|
69
|
+
}
|
|
70
|
+
function getNextMonthlyReset(from = /* @__PURE__ */ new Date()) {
|
|
71
|
+
const next = new Date(from);
|
|
72
|
+
next.setMonth(next.getMonth() + 1);
|
|
73
|
+
next.setDate(1);
|
|
74
|
+
next.setHours(0, 0, 0, 0);
|
|
75
|
+
return next;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// src/config/index.ts
|
|
79
|
+
var import_zod = require("zod");
|
|
80
|
+
|
|
81
|
+
// src/config/provider.ts
|
|
82
|
+
var configProvider = null;
|
|
83
|
+
function getConfigProvider() {
|
|
84
|
+
return configProvider;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// src/config/index.ts
|
|
88
|
+
var tierConfigSchema = import_zod.z.object({
|
|
89
|
+
tier: import_zod.z.enum(["free", "basic", "premium", "unlimited"]),
|
|
90
|
+
monthlyCredits: import_zod.z.number().min(0),
|
|
91
|
+
priceUsd: import_zod.z.number().min(0),
|
|
92
|
+
features: import_zod.z.array(import_zod.z.string())
|
|
93
|
+
});
|
|
94
|
+
var creditSystemConfigSchema = import_zod.z.object({
|
|
95
|
+
/**
|
|
96
|
+
* Operation costs in credits
|
|
97
|
+
* Keys are operation type names (any non-empty string)
|
|
98
|
+
* Values are positive numbers representing credit cost
|
|
99
|
+
*/
|
|
100
|
+
operationCosts: import_zod.z.record(
|
|
101
|
+
import_zod.z.string().min(1),
|
|
102
|
+
import_zod.z.number().positive()
|
|
103
|
+
),
|
|
104
|
+
/**
|
|
105
|
+
* Display labels for operation types (English canonical labels).
|
|
106
|
+
* Falls back to auto-generating from snake_case keys if not provided.
|
|
107
|
+
*/
|
|
108
|
+
operationLabels: import_zod.z.record(
|
|
109
|
+
import_zod.z.string().min(1),
|
|
110
|
+
import_zod.z.string().min(1)
|
|
111
|
+
).optional().default({}),
|
|
112
|
+
/**
|
|
113
|
+
* Tier configurations
|
|
114
|
+
*/
|
|
115
|
+
tierConfigs: import_zod.z.record(
|
|
116
|
+
import_zod.z.enum(["free", "basic", "premium", "unlimited"]),
|
|
117
|
+
tierConfigSchema
|
|
118
|
+
),
|
|
119
|
+
/**
|
|
120
|
+
* Reservation expiry time in milliseconds
|
|
121
|
+
*/
|
|
122
|
+
reservationExpiryMs: import_zod.z.number().positive().default(5 * 60 * 1e3),
|
|
123
|
+
/**
|
|
124
|
+
* Default credits for new users (free tier)
|
|
125
|
+
*/
|
|
126
|
+
defaultFreeCredits: import_zod.z.number().positive().default(25),
|
|
127
|
+
/**
|
|
128
|
+
* Grace period in days after subscription expires before downgrade
|
|
129
|
+
*/
|
|
130
|
+
subscriptionGracePeriodDays: import_zod.z.number().min(0).default(3),
|
|
131
|
+
/**
|
|
132
|
+
* Low balance notification threshold
|
|
133
|
+
*/
|
|
134
|
+
lowBalanceThreshold: import_zod.z.number().min(0).default(10),
|
|
135
|
+
/**
|
|
136
|
+
* Cooldown in hours between low balance notifications
|
|
137
|
+
*/
|
|
138
|
+
lowBalanceNotificationCooldownHours: import_zod.z.number().positive().default(24),
|
|
139
|
+
/**
|
|
140
|
+
* Feature flags for optional features
|
|
141
|
+
*/
|
|
142
|
+
features: import_zod.z.object({
|
|
143
|
+
journalEntries: import_zod.z.boolean().default(true),
|
|
144
|
+
notifications: import_zod.z.boolean().default(true),
|
|
145
|
+
subscriptionExpiry: import_zod.z.boolean().default(true),
|
|
146
|
+
usageHistory: import_zod.z.boolean().default(true)
|
|
147
|
+
}).default({
|
|
148
|
+
journalEntries: true,
|
|
149
|
+
notifications: true,
|
|
150
|
+
subscriptionExpiry: true,
|
|
151
|
+
usageHistory: true
|
|
152
|
+
})
|
|
153
|
+
});
|
|
154
|
+
var DEFAULT_CONFIG = {
|
|
155
|
+
operationCosts: {
|
|
156
|
+
story_generation: 5,
|
|
157
|
+
conversation: 2,
|
|
158
|
+
image_generation: 10,
|
|
159
|
+
template_generation: 10
|
|
160
|
+
},
|
|
161
|
+
operationLabels: {
|
|
162
|
+
story_generation: "Story generation",
|
|
163
|
+
conversation: "Conversation",
|
|
164
|
+
image_generation: "Image generation",
|
|
165
|
+
template_generation: "Template generation"
|
|
166
|
+
},
|
|
167
|
+
tierConfigs: {
|
|
168
|
+
free: {
|
|
169
|
+
tier: "free",
|
|
170
|
+
monthlyCredits: 25,
|
|
171
|
+
priceUsd: 0,
|
|
172
|
+
features: [
|
|
173
|
+
"25 credits per month",
|
|
174
|
+
"Basic story generation",
|
|
175
|
+
"Standard image quality"
|
|
176
|
+
]
|
|
177
|
+
},
|
|
178
|
+
basic: {
|
|
179
|
+
tier: "basic",
|
|
180
|
+
monthlyCredits: 200,
|
|
181
|
+
priceUsd: 9.99,
|
|
182
|
+
features: [
|
|
183
|
+
"200 credits per month",
|
|
184
|
+
"All story features",
|
|
185
|
+
"High quality images",
|
|
186
|
+
"Priority support"
|
|
187
|
+
]
|
|
188
|
+
},
|
|
189
|
+
premium: {
|
|
190
|
+
tier: "premium",
|
|
191
|
+
monthlyCredits: 500,
|
|
192
|
+
priceUsd: 19.99,
|
|
193
|
+
features: [
|
|
194
|
+
"500 credits per month",
|
|
195
|
+
"All features",
|
|
196
|
+
"Highest quality images",
|
|
197
|
+
"Early access to new features",
|
|
198
|
+
"Priority support"
|
|
199
|
+
]
|
|
200
|
+
},
|
|
201
|
+
unlimited: {
|
|
202
|
+
tier: "unlimited",
|
|
203
|
+
monthlyCredits: 0,
|
|
204
|
+
// 0 = unlimited
|
|
205
|
+
priceUsd: 49.99,
|
|
206
|
+
features: [
|
|
207
|
+
"Unlimited credits",
|
|
208
|
+
"All features",
|
|
209
|
+
"Highest quality images",
|
|
210
|
+
"Early access to new features",
|
|
211
|
+
"Dedicated support"
|
|
212
|
+
]
|
|
213
|
+
}
|
|
214
|
+
},
|
|
215
|
+
reservationExpiryMs: 5 * 60 * 1e3,
|
|
216
|
+
// 5 minutes
|
|
217
|
+
defaultFreeCredits: 25,
|
|
218
|
+
subscriptionGracePeriodDays: 3,
|
|
219
|
+
lowBalanceThreshold: 10,
|
|
220
|
+
lowBalanceNotificationCooldownHours: 24,
|
|
221
|
+
features: {
|
|
222
|
+
journalEntries: true,
|
|
223
|
+
notifications: true,
|
|
224
|
+
subscriptionExpiry: true,
|
|
225
|
+
usageHistory: true
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
var currentConfig = DEFAULT_CONFIG;
|
|
229
|
+
function loadConfigFromEnv() {
|
|
230
|
+
const envConfig = {};
|
|
231
|
+
const operationCostsEnv = process.env.CREDITS_OPERATION_COSTS;
|
|
232
|
+
if (operationCostsEnv) {
|
|
233
|
+
try {
|
|
234
|
+
const parsed = JSON.parse(operationCostsEnv);
|
|
235
|
+
if (typeof parsed === "object" && parsed !== null) {
|
|
236
|
+
envConfig.operationCosts = {
|
|
237
|
+
...DEFAULT_CONFIG.operationCosts,
|
|
238
|
+
...parsed
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
} catch {
|
|
242
|
+
console.warn("[Credits Config] Failed to parse CREDITS_OPERATION_COSTS env var as JSON");
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
const reservationExpiry = process.env.CREDITS_RESERVATION_EXPIRY_MS;
|
|
246
|
+
if (reservationExpiry) {
|
|
247
|
+
envConfig.reservationExpiryMs = parseInt(reservationExpiry, 10);
|
|
248
|
+
}
|
|
249
|
+
const defaultCredits = process.env.CREDITS_DEFAULT_FREE;
|
|
250
|
+
if (defaultCredits) {
|
|
251
|
+
envConfig.defaultFreeCredits = parseInt(defaultCredits, 10);
|
|
252
|
+
}
|
|
253
|
+
const gracePeriod = process.env.CREDITS_GRACE_PERIOD_DAYS;
|
|
254
|
+
if (gracePeriod) {
|
|
255
|
+
envConfig.subscriptionGracePeriodDays = parseInt(gracePeriod, 10);
|
|
256
|
+
}
|
|
257
|
+
const lowBalanceThreshold = process.env.CREDITS_LOW_BALANCE_THRESHOLD;
|
|
258
|
+
if (lowBalanceThreshold) {
|
|
259
|
+
envConfig.lowBalanceThreshold = parseInt(lowBalanceThreshold, 10);
|
|
260
|
+
}
|
|
261
|
+
const featuresEnv = process.env.CREDITS_FEATURES;
|
|
262
|
+
if (featuresEnv) {
|
|
263
|
+
try {
|
|
264
|
+
const features = JSON.parse(featuresEnv);
|
|
265
|
+
envConfig.features = {
|
|
266
|
+
...DEFAULT_CONFIG.features,
|
|
267
|
+
...features
|
|
268
|
+
};
|
|
269
|
+
} catch {
|
|
270
|
+
console.warn("[Credits Config] Failed to parse CREDITS_FEATURES env var");
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
return envConfig;
|
|
274
|
+
}
|
|
275
|
+
function initializeConfig(overrides) {
|
|
276
|
+
const envConfig = loadConfigFromEnv();
|
|
277
|
+
const mergedConfig = {
|
|
278
|
+
...DEFAULT_CONFIG,
|
|
279
|
+
...envConfig,
|
|
280
|
+
...overrides,
|
|
281
|
+
operationCosts: {
|
|
282
|
+
...DEFAULT_CONFIG.operationCosts,
|
|
283
|
+
...envConfig.operationCosts,
|
|
284
|
+
...overrides?.operationCosts
|
|
285
|
+
},
|
|
286
|
+
operationLabels: {
|
|
287
|
+
...DEFAULT_CONFIG.operationLabels,
|
|
288
|
+
...envConfig.operationLabels,
|
|
289
|
+
...overrides?.operationLabels
|
|
290
|
+
},
|
|
291
|
+
tierConfigs: {
|
|
292
|
+
...DEFAULT_CONFIG.tierConfigs,
|
|
293
|
+
...envConfig.tierConfigs,
|
|
294
|
+
...overrides?.tierConfigs
|
|
295
|
+
},
|
|
296
|
+
features: {
|
|
297
|
+
...DEFAULT_CONFIG.features,
|
|
298
|
+
...envConfig.features,
|
|
299
|
+
...overrides?.features
|
|
300
|
+
}
|
|
301
|
+
};
|
|
302
|
+
const result = creditSystemConfigSchema.safeParse(mergedConfig);
|
|
303
|
+
if (!result.success) {
|
|
304
|
+
console.error("[Credits Config] Invalid configuration:", result.error.issues);
|
|
305
|
+
throw new Error("Invalid credit system configuration");
|
|
306
|
+
}
|
|
307
|
+
currentConfig = result.data;
|
|
308
|
+
return currentConfig;
|
|
309
|
+
}
|
|
310
|
+
function getConfig() {
|
|
311
|
+
const provider = getConfigProvider();
|
|
312
|
+
if (provider) {
|
|
313
|
+
return provider.getConfig();
|
|
314
|
+
}
|
|
315
|
+
return currentConfig;
|
|
316
|
+
}
|
|
317
|
+
function getConfigMonthlyLimit(tier) {
|
|
318
|
+
const config = getConfig().tierConfigs[tier];
|
|
319
|
+
return config.monthlyCredits === 0 ? Infinity : config.monthlyCredits;
|
|
320
|
+
}
|
|
321
|
+
initializeConfig();
|
|
322
|
+
|
|
323
|
+
// src/repository/memory/index.ts
|
|
324
|
+
var InMemoryCreditRepository = class {
|
|
325
|
+
users = /* @__PURE__ */ new Map();
|
|
326
|
+
reservations = /* @__PURE__ */ new Map();
|
|
327
|
+
transactions = /* @__PURE__ */ new Map();
|
|
328
|
+
usageLogs = [];
|
|
329
|
+
journalEntries = /* @__PURE__ */ new Map();
|
|
330
|
+
// ==================== User Credits ====================
|
|
331
|
+
async getUserCredits(userId) {
|
|
332
|
+
return this.users.get(userId) ?? null;
|
|
333
|
+
}
|
|
334
|
+
async initializeUserCredits(userId, tier, initialBalance) {
|
|
335
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
336
|
+
const credits = {
|
|
337
|
+
userId,
|
|
338
|
+
balance: initialBalance,
|
|
339
|
+
bonusCredits: 0,
|
|
340
|
+
reserved: 0,
|
|
341
|
+
tier,
|
|
342
|
+
monthlyLimit: getConfigMonthlyLimit(tier),
|
|
343
|
+
monthlyUsed: 0,
|
|
344
|
+
monthlyResetAt: getNextMonthlyReset().toISOString(),
|
|
345
|
+
subscriptionExpiresAt: null,
|
|
346
|
+
createdAt: now,
|
|
347
|
+
updatedAt: now
|
|
348
|
+
};
|
|
349
|
+
this.users.set(userId, credits);
|
|
350
|
+
return credits;
|
|
351
|
+
}
|
|
352
|
+
async updateUserCredits(userId, updates) {
|
|
353
|
+
const credits = this.users.get(userId);
|
|
354
|
+
if (!credits) {
|
|
355
|
+
throw new Error(`User ${userId} not found`);
|
|
356
|
+
}
|
|
357
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
358
|
+
if (updates.balance !== void 0) credits.balance = updates.balance;
|
|
359
|
+
if (updates.bonusCredits !== void 0) credits.bonusCredits = updates.bonusCredits;
|
|
360
|
+
if (updates.reserved !== void 0) credits.reserved = updates.reserved;
|
|
361
|
+
if (updates.tier !== void 0) credits.tier = updates.tier;
|
|
362
|
+
if (updates.monthlyLimit !== void 0) credits.monthlyLimit = updates.monthlyLimit;
|
|
363
|
+
if (updates.monthlyUsed !== void 0) credits.monthlyUsed = updates.monthlyUsed;
|
|
364
|
+
if (updates.monthlyResetAt !== void 0) {
|
|
365
|
+
credits.monthlyResetAt = updates.monthlyResetAt instanceof Date ? updates.monthlyResetAt.toISOString() : updates.monthlyResetAt;
|
|
366
|
+
}
|
|
367
|
+
if (updates.subscriptionExpiresAt !== void 0) {
|
|
368
|
+
if (updates.subscriptionExpiresAt === null) {
|
|
369
|
+
credits.subscriptionExpiresAt = null;
|
|
370
|
+
} else {
|
|
371
|
+
credits.subscriptionExpiresAt = updates.subscriptionExpiresAt instanceof Date ? updates.subscriptionExpiresAt.toISOString() : updates.subscriptionExpiresAt;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
if (updates.balanceIncrement !== void 0) {
|
|
375
|
+
credits.balance += updates.balanceIncrement;
|
|
376
|
+
}
|
|
377
|
+
if (updates.bonusCreditsIncrement !== void 0) {
|
|
378
|
+
credits.bonusCredits += updates.bonusCreditsIncrement;
|
|
379
|
+
}
|
|
380
|
+
if (updates.reservedIncrement !== void 0) {
|
|
381
|
+
credits.reserved += updates.reservedIncrement;
|
|
382
|
+
}
|
|
383
|
+
if (updates.monthlyUsedIncrement !== void 0) {
|
|
384
|
+
credits.monthlyUsed += updates.monthlyUsedIncrement;
|
|
385
|
+
}
|
|
386
|
+
credits.updatedAt = now;
|
|
387
|
+
this.users.set(userId, credits);
|
|
388
|
+
}
|
|
389
|
+
async updateUserTier(userId, input) {
|
|
390
|
+
const credits = this.users.get(userId);
|
|
391
|
+
if (!credits) {
|
|
392
|
+
throw new Error(`User ${userId} not found`);
|
|
393
|
+
}
|
|
394
|
+
credits.tier = input.tier;
|
|
395
|
+
credits.monthlyLimit = input.monthlyLimit;
|
|
396
|
+
if (input.balance !== void 0) credits.balance = input.balance;
|
|
397
|
+
if (input.monthlyUsed !== void 0) credits.monthlyUsed = input.monthlyUsed;
|
|
398
|
+
if (input.subscriptionExpiresAt !== void 0) {
|
|
399
|
+
if (input.subscriptionExpiresAt === null) {
|
|
400
|
+
credits.subscriptionExpiresAt = null;
|
|
401
|
+
} else {
|
|
402
|
+
credits.subscriptionExpiresAt = input.subscriptionExpiresAt instanceof Date ? input.subscriptionExpiresAt.toISOString() : input.subscriptionExpiresAt;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
credits.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
406
|
+
this.users.set(userId, credits);
|
|
407
|
+
}
|
|
408
|
+
// ==================== Reservations ====================
|
|
409
|
+
async createReservation(input) {
|
|
410
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
411
|
+
const reservation = {
|
|
412
|
+
id: generateId(),
|
|
413
|
+
userId: input.userId,
|
|
414
|
+
amount: input.amount,
|
|
415
|
+
operationType: input.operationType,
|
|
416
|
+
status: "reserved",
|
|
417
|
+
createdAt: now,
|
|
418
|
+
expiresAt: input.expiresAt.toISOString()
|
|
419
|
+
};
|
|
420
|
+
if (!this.reservations.has(input.userId)) {
|
|
421
|
+
this.reservations.set(input.userId, /* @__PURE__ */ new Map());
|
|
422
|
+
}
|
|
423
|
+
this.reservations.get(input.userId).set(reservation.id, reservation);
|
|
424
|
+
return reservation;
|
|
425
|
+
}
|
|
426
|
+
async getReservation(userId, reservationId) {
|
|
427
|
+
const userReservations = this.reservations.get(userId);
|
|
428
|
+
if (!userReservations) return null;
|
|
429
|
+
return userReservations.get(reservationId) ?? null;
|
|
430
|
+
}
|
|
431
|
+
async updateReservationStatus(userId, reservationId, status, completedAt) {
|
|
432
|
+
const userReservations = this.reservations.get(userId);
|
|
433
|
+
if (!userReservations) {
|
|
434
|
+
throw new Error(`No reservations found for user ${userId}`);
|
|
435
|
+
}
|
|
436
|
+
const reservation = userReservations.get(reservationId);
|
|
437
|
+
if (!reservation) {
|
|
438
|
+
throw new Error(`Reservation ${reservationId} not found`);
|
|
439
|
+
}
|
|
440
|
+
reservation.status = status;
|
|
441
|
+
if (completedAt) reservation.completedAt = completedAt.toISOString();
|
|
442
|
+
userReservations.set(reservationId, reservation);
|
|
443
|
+
}
|
|
444
|
+
// ==================== Atomic Operations ====================
|
|
445
|
+
async reserveCreditsAtomic(userId, amount, operationType, expiresAt) {
|
|
446
|
+
const credits = this.users.get(userId);
|
|
447
|
+
if (!credits) {
|
|
448
|
+
throw new Error(`User ${userId} not found`);
|
|
449
|
+
}
|
|
450
|
+
const available = credits.balance + credits.bonusCredits - credits.reserved;
|
|
451
|
+
if (available < amount) {
|
|
452
|
+
throw new Error(
|
|
453
|
+
`Insufficient credits. Available: ${available}, Required: ${amount}`
|
|
454
|
+
);
|
|
455
|
+
}
|
|
456
|
+
const reservation = await this.createReservation({
|
|
457
|
+
userId,
|
|
458
|
+
amount,
|
|
459
|
+
operationType,
|
|
460
|
+
expiresAt
|
|
461
|
+
});
|
|
462
|
+
credits.reserved += amount;
|
|
463
|
+
credits.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
464
|
+
this.users.set(userId, credits);
|
|
465
|
+
return reservation;
|
|
466
|
+
}
|
|
467
|
+
async commitReservationAtomic(userId, reservationId) {
|
|
468
|
+
const credits = this.users.get(userId);
|
|
469
|
+
if (!credits) {
|
|
470
|
+
throw new Error(`User ${userId} not found`);
|
|
471
|
+
}
|
|
472
|
+
const reservation = await this.getReservation(userId, reservationId);
|
|
473
|
+
if (!reservation) {
|
|
474
|
+
throw new Error(`Reservation ${reservationId} not found`);
|
|
475
|
+
}
|
|
476
|
+
if (reservation.status === "committed") {
|
|
477
|
+
return;
|
|
478
|
+
}
|
|
479
|
+
if (reservation.status !== "reserved") {
|
|
480
|
+
throw new Error(
|
|
481
|
+
`Cannot commit reservation in ${reservation.status} state`
|
|
482
|
+
);
|
|
483
|
+
}
|
|
484
|
+
const amount = reservation.amount;
|
|
485
|
+
credits.balance -= amount;
|
|
486
|
+
credits.reserved -= amount;
|
|
487
|
+
credits.monthlyUsed += amount;
|
|
488
|
+
credits.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
489
|
+
this.users.set(userId, credits);
|
|
490
|
+
await this.updateReservationStatus(userId, reservationId, "committed", /* @__PURE__ */ new Date());
|
|
491
|
+
}
|
|
492
|
+
async releaseReservationAtomic(userId, reservationId) {
|
|
493
|
+
const credits = this.users.get(userId);
|
|
494
|
+
if (!credits) {
|
|
495
|
+
throw new Error(`User ${userId} not found`);
|
|
496
|
+
}
|
|
497
|
+
const reservation = await this.getReservation(userId, reservationId);
|
|
498
|
+
if (!reservation) {
|
|
499
|
+
throw new Error(`Reservation ${reservationId} not found`);
|
|
500
|
+
}
|
|
501
|
+
if (reservation.status !== "reserved") {
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
504
|
+
credits.reserved -= reservation.amount;
|
|
505
|
+
credits.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
506
|
+
this.users.set(userId, credits);
|
|
507
|
+
await this.updateReservationStatus(userId, reservationId, "released", /* @__PURE__ */ new Date());
|
|
508
|
+
}
|
|
509
|
+
async addCreditsAtomic(userId, amount, description, paymentRef) {
|
|
510
|
+
const credits = this.users.get(userId);
|
|
511
|
+
if (!credits) {
|
|
512
|
+
throw new Error(`User ${userId} not found`);
|
|
513
|
+
}
|
|
514
|
+
const previousBalance = credits.bonusCredits;
|
|
515
|
+
credits.bonusCredits += amount;
|
|
516
|
+
credits.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
517
|
+
this.users.set(userId, credits);
|
|
518
|
+
await this.createTransaction({
|
|
519
|
+
userId,
|
|
520
|
+
type: "purchase",
|
|
521
|
+
amount,
|
|
522
|
+
description,
|
|
523
|
+
paymentRef,
|
|
524
|
+
previousBalance,
|
|
525
|
+
newBalance: credits.bonusCredits
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
async deductCreditsAtomic(userId, amount) {
|
|
529
|
+
if (amount <= 0) {
|
|
530
|
+
throw new Error(`deductCreditsAtomic amount must be positive (got ${amount})`);
|
|
531
|
+
}
|
|
532
|
+
const credits = this.users.get(userId);
|
|
533
|
+
if (!credits) {
|
|
534
|
+
throw new Error(`User credits not found for userId: ${userId}`);
|
|
535
|
+
}
|
|
536
|
+
const available = credits.balance + credits.bonusCredits - credits.reserved;
|
|
537
|
+
if (available < amount) {
|
|
538
|
+
throw new Error(
|
|
539
|
+
`Insufficient credits. Available: ${available}, requested: ${amount}`
|
|
540
|
+
);
|
|
541
|
+
}
|
|
542
|
+
const balanceDeduction = Math.min(credits.balance, amount);
|
|
543
|
+
const bonusDeduction = amount - balanceDeduction;
|
|
544
|
+
const previousBalance = credits.balance + credits.bonusCredits;
|
|
545
|
+
credits.balance -= balanceDeduction;
|
|
546
|
+
credits.bonusCredits -= bonusDeduction;
|
|
547
|
+
credits.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
548
|
+
this.users.set(userId, credits);
|
|
549
|
+
return { previousBalance, newBalance: previousBalance - amount };
|
|
550
|
+
}
|
|
551
|
+
// ==================== Transactions ====================
|
|
552
|
+
async createTransaction(input) {
|
|
553
|
+
const transaction = {
|
|
554
|
+
id: generateId(),
|
|
555
|
+
userId: input.userId,
|
|
556
|
+
type: input.type,
|
|
557
|
+
amount: input.amount,
|
|
558
|
+
description: input.description,
|
|
559
|
+
paymentRef: input.paymentRef,
|
|
560
|
+
previousBalance: input.previousBalance,
|
|
561
|
+
newBalance: input.newBalance,
|
|
562
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
563
|
+
};
|
|
564
|
+
if (!this.transactions.has(input.userId)) {
|
|
565
|
+
this.transactions.set(input.userId, []);
|
|
566
|
+
}
|
|
567
|
+
this.transactions.get(input.userId).push(transaction);
|
|
568
|
+
return transaction;
|
|
569
|
+
}
|
|
570
|
+
async getTransactions(userId, limit = 50, offset = 0) {
|
|
571
|
+
const userTransactions = this.transactions.get(userId) ?? [];
|
|
572
|
+
const sorted = [...userTransactions].sort((a, b) => {
|
|
573
|
+
const aDate = toDate(a.createdAt).getTime();
|
|
574
|
+
const bDate = toDate(b.createdAt).getTime();
|
|
575
|
+
return bDate - aDate;
|
|
576
|
+
});
|
|
577
|
+
return sorted.slice(offset, offset + limit);
|
|
578
|
+
}
|
|
579
|
+
// ==================== Usage Logs ====================
|
|
580
|
+
async logUsage(input) {
|
|
581
|
+
const log = {
|
|
582
|
+
id: generateId(),
|
|
583
|
+
userId: input.userId,
|
|
584
|
+
operationType: input.operationType,
|
|
585
|
+
provider: input.provider,
|
|
586
|
+
creditsUsed: input.creditsUsed,
|
|
587
|
+
success: input.success,
|
|
588
|
+
errorMessage: input.errorMessage,
|
|
589
|
+
resourceId: input.resourceId,
|
|
590
|
+
resourceType: input.resourceType,
|
|
591
|
+
requestId: input.requestId,
|
|
592
|
+
metadata: input.metadata,
|
|
593
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
594
|
+
};
|
|
595
|
+
this.usageLogs.push(log);
|
|
596
|
+
return log;
|
|
597
|
+
}
|
|
598
|
+
async getUsageLogs(query) {
|
|
599
|
+
let results = [...this.usageLogs];
|
|
600
|
+
if (query.userId) {
|
|
601
|
+
results = results.filter((log) => log.userId === query.userId);
|
|
602
|
+
}
|
|
603
|
+
if (query.operationType) {
|
|
604
|
+
results = results.filter((log) => log.operationType === query.operationType);
|
|
605
|
+
}
|
|
606
|
+
if (query.success !== void 0) {
|
|
607
|
+
results = results.filter((log) => log.success === query.success);
|
|
608
|
+
}
|
|
609
|
+
if (query.startDate) {
|
|
610
|
+
const startTime = query.startDate.getTime();
|
|
611
|
+
results = results.filter(
|
|
612
|
+
(log) => toDate(log.createdAt).getTime() >= startTime
|
|
613
|
+
);
|
|
614
|
+
}
|
|
615
|
+
if (query.endDate) {
|
|
616
|
+
const endTime = query.endDate.getTime();
|
|
617
|
+
results = results.filter(
|
|
618
|
+
(log) => toDate(log.createdAt).getTime() <= endTime
|
|
619
|
+
);
|
|
620
|
+
}
|
|
621
|
+
results.sort((a, b) => {
|
|
622
|
+
return toDate(b.createdAt).getTime() - toDate(a.createdAt).getTime();
|
|
623
|
+
});
|
|
624
|
+
const offset = query.offset ?? 0;
|
|
625
|
+
const limit = query.limit ?? 50;
|
|
626
|
+
return results.slice(offset, offset + limit);
|
|
627
|
+
}
|
|
628
|
+
async getUsageLogsCount(query) {
|
|
629
|
+
const results = await this.getUsageLogs({ ...query, limit: Infinity, offset: 0 });
|
|
630
|
+
return results.length;
|
|
631
|
+
}
|
|
632
|
+
// ==================== Journal Entries ====================
|
|
633
|
+
async createJournalEntry(input) {
|
|
634
|
+
const entry = {
|
|
635
|
+
id: generateId(),
|
|
636
|
+
userId: input.userId,
|
|
637
|
+
entryType: input.entryType,
|
|
638
|
+
amount: input.amount,
|
|
639
|
+
balanceAfter: input.balanceAfter,
|
|
640
|
+
source: input.source,
|
|
641
|
+
referenceId: input.referenceId,
|
|
642
|
+
referenceType: input.referenceType,
|
|
643
|
+
description: input.description,
|
|
644
|
+
metadata: input.metadata,
|
|
645
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
646
|
+
};
|
|
647
|
+
if (!this.journalEntries.has(input.userId)) {
|
|
648
|
+
this.journalEntries.set(input.userId, []);
|
|
649
|
+
}
|
|
650
|
+
this.journalEntries.get(input.userId).push(entry);
|
|
651
|
+
return entry;
|
|
652
|
+
}
|
|
653
|
+
async getJournalEntries(query) {
|
|
654
|
+
let results = this.journalEntries.get(query.userId) ?? [];
|
|
655
|
+
if (query.source) {
|
|
656
|
+
results = results.filter((entry) => entry.source === query.source);
|
|
657
|
+
}
|
|
658
|
+
if (query.referenceType) {
|
|
659
|
+
results = results.filter((entry) => entry.referenceType === query.referenceType);
|
|
660
|
+
}
|
|
661
|
+
if (query.startDate) {
|
|
662
|
+
const startTime = query.startDate.getTime();
|
|
663
|
+
results = results.filter(
|
|
664
|
+
(entry) => toDate(entry.createdAt).getTime() >= startTime
|
|
665
|
+
);
|
|
666
|
+
}
|
|
667
|
+
if (query.endDate) {
|
|
668
|
+
const endTime = query.endDate.getTime();
|
|
669
|
+
results = results.filter(
|
|
670
|
+
(entry) => toDate(entry.createdAt).getTime() <= endTime
|
|
671
|
+
);
|
|
672
|
+
}
|
|
673
|
+
results = [...results].sort((a, b) => {
|
|
674
|
+
return toDate(b.createdAt).getTime() - toDate(a.createdAt).getTime();
|
|
675
|
+
});
|
|
676
|
+
const offset = query.offset ?? 0;
|
|
677
|
+
const limit = query.limit ?? 50;
|
|
678
|
+
return results.slice(offset, offset + limit);
|
|
679
|
+
}
|
|
680
|
+
async getJournalEntriesCount(query) {
|
|
681
|
+
const results = await this.getJournalEntries({
|
|
682
|
+
...query,
|
|
683
|
+
limit: Infinity,
|
|
684
|
+
offset: 0
|
|
685
|
+
});
|
|
686
|
+
return results.length;
|
|
687
|
+
}
|
|
688
|
+
// ==================== Cleanup Operations ====================
|
|
689
|
+
async findAndExpireReservations(_batchSize = 100, _maxIterations = 100) {
|
|
690
|
+
const now = /* @__PURE__ */ new Date();
|
|
691
|
+
let expiredCount = 0;
|
|
692
|
+
let creditsReleased = 0;
|
|
693
|
+
const errors = [];
|
|
694
|
+
for (const [userId, userReservations] of this.reservations) {
|
|
695
|
+
for (const [reservationId, reservation] of userReservations) {
|
|
696
|
+
if (reservation.status === "reserved" && toDate(reservation.expiresAt).getTime() < now.getTime()) {
|
|
697
|
+
try {
|
|
698
|
+
await this.releaseReservationAtomic(userId, reservationId);
|
|
699
|
+
reservation.status = "expired";
|
|
700
|
+
userReservations.set(reservationId, reservation);
|
|
701
|
+
expiredCount++;
|
|
702
|
+
creditsReleased += reservation.amount;
|
|
703
|
+
} catch (error) {
|
|
704
|
+
errors.push(
|
|
705
|
+
`Failed to expire reservation ${reservationId}: ${error}`
|
|
706
|
+
);
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
return { expiredCount, creditsReleased, errors };
|
|
712
|
+
}
|
|
713
|
+
// ==================== Atomic Monthly Reset ====================
|
|
714
|
+
async atomicMonthlyReset(userId, tier, expectedResetAt) {
|
|
715
|
+
const credits = this.users.get(userId);
|
|
716
|
+
if (!credits) {
|
|
717
|
+
throw new Error(`User ${userId} not found`);
|
|
718
|
+
}
|
|
719
|
+
const currentResetAt = toDate(credits.monthlyResetAt).getTime();
|
|
720
|
+
const expected = toDate(expectedResetAt).getTime();
|
|
721
|
+
if (currentResetAt !== expected) {
|
|
722
|
+
return { wasReset: false, credits };
|
|
723
|
+
}
|
|
724
|
+
const newBalance = getConfigMonthlyLimit(tier);
|
|
725
|
+
const nextReset = getNextMonthlyReset();
|
|
726
|
+
credits.balance = newBalance === Infinity ? credits.balance : newBalance;
|
|
727
|
+
credits.monthlyUsed = 0;
|
|
728
|
+
credits.monthlyResetAt = nextReset.toISOString();
|
|
729
|
+
credits.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
730
|
+
this.users.set(userId, credits);
|
|
731
|
+
return { wasReset: true, credits };
|
|
732
|
+
}
|
|
733
|
+
// ==================== Subscription Expiry ====================
|
|
734
|
+
async checkAndHandleSubscriptionExpiry(userId, gracePeriodDays = 3) {
|
|
735
|
+
const credits = this.users.get(userId);
|
|
736
|
+
if (!credits) {
|
|
737
|
+
throw new Error(`User ${userId} not found`);
|
|
738
|
+
}
|
|
739
|
+
if (credits.tier === "free" || !credits.subscriptionExpiresAt) {
|
|
740
|
+
return {
|
|
741
|
+
wasDowngraded: false,
|
|
742
|
+
inGracePeriod: false,
|
|
743
|
+
graceDaysRemaining: 0,
|
|
744
|
+
credits
|
|
745
|
+
};
|
|
746
|
+
}
|
|
747
|
+
const now = /* @__PURE__ */ new Date();
|
|
748
|
+
const expiresAt = toDate(credits.subscriptionExpiresAt);
|
|
749
|
+
const daysSinceExpiry = (now.getTime() - expiresAt.getTime()) / (1e3 * 60 * 60 * 24);
|
|
750
|
+
if (daysSinceExpiry <= 0) {
|
|
751
|
+
return {
|
|
752
|
+
wasDowngraded: false,
|
|
753
|
+
inGracePeriod: false,
|
|
754
|
+
graceDaysRemaining: 0,
|
|
755
|
+
credits
|
|
756
|
+
};
|
|
757
|
+
}
|
|
758
|
+
if (daysSinceExpiry <= gracePeriodDays) {
|
|
759
|
+
return {
|
|
760
|
+
wasDowngraded: false,
|
|
761
|
+
inGracePeriod: true,
|
|
762
|
+
graceDaysRemaining: Math.ceil(gracePeriodDays - daysSinceExpiry),
|
|
763
|
+
credits
|
|
764
|
+
};
|
|
765
|
+
}
|
|
766
|
+
const config = getConfig();
|
|
767
|
+
const freeTierConfig = config.tierConfigs.free;
|
|
768
|
+
credits.tier = "free";
|
|
769
|
+
credits.monthlyLimit = freeTierConfig.monthlyCredits;
|
|
770
|
+
credits.balance = Math.min(credits.balance, freeTierConfig.monthlyCredits);
|
|
771
|
+
credits.subscriptionExpiresAt = null;
|
|
772
|
+
credits.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
773
|
+
this.users.set(userId, credits);
|
|
774
|
+
return {
|
|
775
|
+
wasDowngraded: true,
|
|
776
|
+
inGracePeriod: false,
|
|
777
|
+
graceDaysRemaining: 0,
|
|
778
|
+
credits
|
|
779
|
+
};
|
|
780
|
+
}
|
|
781
|
+
// ==================== Testing Utilities ====================
|
|
782
|
+
/**
|
|
783
|
+
* Clear all data (useful for testing)
|
|
784
|
+
*/
|
|
785
|
+
clear() {
|
|
786
|
+
this.users.clear();
|
|
787
|
+
this.reservations.clear();
|
|
788
|
+
this.transactions.clear();
|
|
789
|
+
this.usageLogs = [];
|
|
790
|
+
this.journalEntries.clear();
|
|
791
|
+
}
|
|
792
|
+
/**
|
|
793
|
+
* Get all users (useful for testing/debugging)
|
|
794
|
+
*/
|
|
795
|
+
getAllUsers() {
|
|
796
|
+
return Array.from(this.users.values());
|
|
797
|
+
}
|
|
798
|
+
/**
|
|
799
|
+
* Get all reservations for a user (useful for testing)
|
|
800
|
+
*/
|
|
801
|
+
getAllReservations(userId) {
|
|
802
|
+
const userReservations = this.reservations.get(userId);
|
|
803
|
+
if (!userReservations) return [];
|
|
804
|
+
return Array.from(userReservations.values());
|
|
805
|
+
}
|
|
806
|
+
};
|
|
807
|
+
function createInMemoryCreditRepository() {
|
|
808
|
+
return new InMemoryCreditRepository();
|
|
809
|
+
}
|
|
810
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
811
|
+
0 && (module.exports = {
|
|
812
|
+
InMemoryCreditRepository,
|
|
813
|
+
createInMemoryCreditRepository,
|
|
814
|
+
generateId,
|
|
815
|
+
getNextMonthlyReset,
|
|
816
|
+
toClientUserCredits,
|
|
817
|
+
toDate
|
|
818
|
+
});
|
|
819
|
+
//# sourceMappingURL=index.cjs.map
|