@nehorai/credits 3.0.0 → 3.1.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 +169 -156
- package/dist/adapters/index.cjs.map +1 -1
- package/dist/adapters/index.d.cts +1 -1
- package/dist/adapters/index.d.ts +1 -1
- package/dist/adapters/index.js +169 -156
- package/dist/adapters/index.js.map +1 -1
- package/dist/core/index.cjs +9 -1
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +2 -2
- package/dist/core/index.d.ts +2 -2
- package/dist/core/index.js +9 -1
- package/dist/core/index.js.map +1 -1
- package/dist/index-BrogG0Gc.d.cts +277 -0
- package/dist/index-DOvJES2m.d.ts +277 -0
- package/dist/index.cjs +78 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +76 -10
- package/dist/index.js.map +1 -1
- package/dist/repository/index.cjs +71 -8
- package/dist/repository/index.cjs.map +1 -1
- package/dist/repository/index.d.cts +3 -255
- package/dist/repository/index.d.ts +3 -255
- package/dist/repository/index.js +71 -8
- package/dist/repository/index.js.map +1 -1
- package/dist/service/index.cjs +17 -3
- package/dist/service/index.cjs.map +1 -1
- package/dist/service/index.d.cts +1 -1
- package/dist/service/index.d.ts +1 -1
- package/dist/service/index.js +17 -3
- package/dist/service/index.js.map +1 -1
- package/dist/{types-CY-oHIMA.d.ts → types-BL3YqxUD.d.ts} +9 -3
- package/dist/{types-DOjguhEX.d.cts → types-YLAB8bFk.d.cts} +9 -3
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -169,6 +169,7 @@ __export(src_exports, {
|
|
|
169
169
|
resetConfig: () => resetConfig,
|
|
170
170
|
sameAmount: () => sameAmount,
|
|
171
171
|
settleExpiredHoldBacking: () => settleExpiredHoldBacking,
|
|
172
|
+
signupGrantJournalInput: () => signupGrantJournalInput,
|
|
172
173
|
spendableBalance: () => spendableBalance,
|
|
173
174
|
storedAmountCeil: () => storedAmountCeil,
|
|
174
175
|
storedAmountFloor: () => storedAmountFloor,
|
|
@@ -178,6 +179,7 @@ __export(src_exports, {
|
|
|
178
179
|
supportsIdempotentCredit: () => supportsIdempotentCredit,
|
|
179
180
|
synchronousDeferred: () => synchronousDeferred,
|
|
180
181
|
terminalStatusOf: () => terminalStatusOf,
|
|
182
|
+
tierChangeJournalInput: () => tierChangeJournalInput,
|
|
181
183
|
tierSchema: () => tierSchema,
|
|
182
184
|
toCents: () => toCents,
|
|
183
185
|
toClientUserCredits: () => toClientUserCredits,
|
|
@@ -1104,17 +1106,25 @@ var DEFAULT_CONFIG = {
|
|
|
1104
1106
|
}
|
|
1105
1107
|
};
|
|
1106
1108
|
var currentConfig = DEFAULT_CONFIG;
|
|
1109
|
+
function isOperationCostMap(value) {
|
|
1110
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
|
|
1111
|
+
return Object.values(value).every((cost) => isRepresentableAmount(cost) && cost >= 0);
|
|
1112
|
+
}
|
|
1107
1113
|
function loadConfigFromEnv() {
|
|
1108
1114
|
const envConfig = {};
|
|
1109
1115
|
const operationCostsEnv = process.env.CREDITS_OPERATION_COSTS;
|
|
1110
1116
|
if (operationCostsEnv) {
|
|
1111
1117
|
try {
|
|
1112
1118
|
const parsed = JSON.parse(operationCostsEnv);
|
|
1113
|
-
if (
|
|
1119
|
+
if (isOperationCostMap(parsed)) {
|
|
1114
1120
|
envConfig.operationCosts = {
|
|
1115
1121
|
...DEFAULT_CONFIG.operationCosts,
|
|
1116
1122
|
...parsed
|
|
1117
1123
|
};
|
|
1124
|
+
} else {
|
|
1125
|
+
console.warn(
|
|
1126
|
+
"[Credits Config] Ignoring CREDITS_OPERATION_COSTS: it must be a JSON object whose costs are numbers >= 0, exact to the cent"
|
|
1127
|
+
);
|
|
1118
1128
|
}
|
|
1119
1129
|
} catch {
|
|
1120
1130
|
console.warn("[Credits Config] Failed to parse CREDITS_OPERATION_COSTS env var as JSON");
|
|
@@ -1562,6 +1572,50 @@ function assertValidBalanceUpdate(current, updates) {
|
|
|
1562
1572
|
throw new Error(`Invalid balance update: ${result.errors.join("; ")}`);
|
|
1563
1573
|
}
|
|
1564
1574
|
}
|
|
1575
|
+
function signupGrantJournalInput(userId, tier, grant) {
|
|
1576
|
+
if (grant <= 0) return null;
|
|
1577
|
+
return {
|
|
1578
|
+
userId,
|
|
1579
|
+
entryType: "credit",
|
|
1580
|
+
amount: grant,
|
|
1581
|
+
balanceBefore: 0,
|
|
1582
|
+
balanceAfter: grant,
|
|
1583
|
+
source: "bonus",
|
|
1584
|
+
referenceId: `signup-${userId}`,
|
|
1585
|
+
referenceType: "adjustment",
|
|
1586
|
+
description: `Signup grant for ${tier} tier.`,
|
|
1587
|
+
metadata: { tier }
|
|
1588
|
+
};
|
|
1589
|
+
}
|
|
1590
|
+
function tierChangeJournalInput(input) {
|
|
1591
|
+
const { userId, previousTier, newTier, previousBalance, newBalance, bonusCredits } = input;
|
|
1592
|
+
const change = sumAmounts(newBalance, -previousBalance);
|
|
1593
|
+
if (change === 0) return null;
|
|
1594
|
+
const raised = change > 0;
|
|
1595
|
+
const amount = raised ? newBalance : 0;
|
|
1596
|
+
const balanceBefore = sumAmounts(previousBalance, bonusCredits);
|
|
1597
|
+
const balanceAfter = sumAmounts(newBalance, bonusCredits);
|
|
1598
|
+
assertRepresentableFields(
|
|
1599
|
+
{
|
|
1600
|
+
"journal amount": amount,
|
|
1601
|
+
"journal balanceBefore": balanceBefore,
|
|
1602
|
+
"journal balanceAfter": balanceAfter
|
|
1603
|
+
},
|
|
1604
|
+
{ userId, operation: "updateUserTier" }
|
|
1605
|
+
);
|
|
1606
|
+
return {
|
|
1607
|
+
userId,
|
|
1608
|
+
entryType: raised ? "credit" : "debit",
|
|
1609
|
+
amount,
|
|
1610
|
+
balanceBefore,
|
|
1611
|
+
balanceAfter,
|
|
1612
|
+
source: raised ? "subscription_upgrade" : "subscription_downgrade",
|
|
1613
|
+
referenceId: `tier-${Date.now()}`,
|
|
1614
|
+
referenceType: "subscription",
|
|
1615
|
+
description: `Tier changed from ${previousTier} to ${newTier}.`,
|
|
1616
|
+
metadata: { previousTier, newTier, previousBalance, newBalance }
|
|
1617
|
+
};
|
|
1618
|
+
}
|
|
1565
1619
|
|
|
1566
1620
|
// src/repository/memory/snapshot.ts
|
|
1567
1621
|
function copyRecord(record) {
|
|
@@ -2105,6 +2159,8 @@ var InMemoryCreditRepository = class {
|
|
|
2105
2159
|
updatedAt: now
|
|
2106
2160
|
};
|
|
2107
2161
|
this.store.users.set(userId, credits);
|
|
2162
|
+
const grantLine = signupGrantJournalInput(userId, tier, initialBalance);
|
|
2163
|
+
if (grantLine) this.recordJournalEntry(grantLine);
|
|
2108
2164
|
return credits;
|
|
2109
2165
|
}
|
|
2110
2166
|
async updateUserCredits(userId, updates) {
|
|
@@ -2185,21 +2241,29 @@ var InMemoryCreditRepository = class {
|
|
|
2185
2241
|
if (!credits) {
|
|
2186
2242
|
throw new Error(`User ${userId} not found`);
|
|
2187
2243
|
}
|
|
2188
|
-
|
|
2189
|
-
|
|
2244
|
+
let planned;
|
|
2245
|
+
if (input.balance !== void 0) {
|
|
2246
|
+
planned = planBalanceTarget(input.balance, credits.reserved, credits.bonusCredits);
|
|
2247
|
+
} else if (input.balanceCap !== void 0 && !isFreeTier(credits.tier)) {
|
|
2248
|
+
planned = planBalanceCap(
|
|
2190
2249
|
credits.balance,
|
|
2191
2250
|
input.balanceCap,
|
|
2192
2251
|
credits.reserved,
|
|
2193
2252
|
credits.bonusCredits,
|
|
2194
2253
|
credits.expiredHoldBacking
|
|
2195
2254
|
);
|
|
2196
|
-
credits.balance = capped.balance;
|
|
2197
|
-
writeExpiredHoldBacking(this.store, credits, capped.expiredHoldBacking);
|
|
2198
2255
|
}
|
|
2256
|
+
const journalLine = planned ? tierChangeJournalInput({
|
|
2257
|
+
userId,
|
|
2258
|
+
previousTier: credits.tier,
|
|
2259
|
+
newTier: input.tier,
|
|
2260
|
+
previousBalance: credits.balance,
|
|
2261
|
+
newBalance: planned.balance,
|
|
2262
|
+
bonusCredits: credits.bonusCredits
|
|
2263
|
+
}) : null;
|
|
2199
2264
|
credits.tier = input.tier;
|
|
2200
2265
|
credits.monthlyLimit = input.monthlyLimit;
|
|
2201
|
-
if (
|
|
2202
|
-
const planned = planBalanceTarget(input.balance, credits.reserved, credits.bonusCredits);
|
|
2266
|
+
if (planned) {
|
|
2203
2267
|
credits.balance = planned.balance;
|
|
2204
2268
|
writeExpiredHoldBacking(this.store, credits, planned.expiredHoldBacking);
|
|
2205
2269
|
}
|
|
@@ -2218,6 +2282,7 @@ var InMemoryCreditRepository = class {
|
|
|
2218
2282
|
}
|
|
2219
2283
|
credits.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2220
2284
|
this.store.users.set(userId, credits);
|
|
2285
|
+
if (journalLine) this.recordJournalEntry(journalLine);
|
|
2221
2286
|
}
|
|
2222
2287
|
// ==================== Reservations ====================
|
|
2223
2288
|
async createReservation(input) {
|
|
@@ -2839,7 +2904,7 @@ var InMemoryCreditRepository = class {
|
|
|
2839
2904
|
userId,
|
|
2840
2905
|
entryType: "debit",
|
|
2841
2906
|
amount: 0,
|
|
2842
|
-
//
|
|
2907
|
+
// 0, however far the cap lowered the balance: the totals record the move
|
|
2843
2908
|
balanceBefore: sumAmounts(previousBalance, credits.bonusCredits),
|
|
2844
2909
|
balanceAfter,
|
|
2845
2910
|
source: "subscription_downgrade",
|
|
@@ -3003,7 +3068,7 @@ var CreditsService = class {
|
|
|
3003
3068
|
userId,
|
|
3004
3069
|
entryType: "debit",
|
|
3005
3070
|
amount: 0,
|
|
3006
|
-
//
|
|
3071
|
+
// 0, however far the cap lowered the balance: the totals record the move
|
|
3007
3072
|
balanceBefore: sumAmounts(data.balance, data.bonusCredits),
|
|
3008
3073
|
// The ledger balance, matching every other journal writer: the V2
|
|
3009
3074
|
// transitions and add/deduct all record `balance + bonusCredits`, so
|
|
@@ -3395,7 +3460,8 @@ var CreditsService = class {
|
|
|
3395
3460
|
]);
|
|
3396
3461
|
const historyEntries = entries.map((entry) => {
|
|
3397
3462
|
const type = this.mapSourceToHistoryType(entry.source);
|
|
3398
|
-
const
|
|
3463
|
+
const signedAmount = entry.entryType === "credit" ? entry.amount : -entry.amount;
|
|
3464
|
+
const creditsChange = entry.balanceBefore !== void 0 ? sumAmounts(entry.balanceAfter, -entry.balanceBefore) : signedAmount;
|
|
3399
3465
|
return {
|
|
3400
3466
|
id: entry.id,
|
|
3401
3467
|
type,
|
|
@@ -4468,6 +4534,7 @@ function createDummyReservation2(userId, operationType) {
|
|
|
4468
4534
|
resetConfig,
|
|
4469
4535
|
sameAmount,
|
|
4470
4536
|
settleExpiredHoldBacking,
|
|
4537
|
+
signupGrantJournalInput,
|
|
4471
4538
|
spendableBalance,
|
|
4472
4539
|
storedAmountCeil,
|
|
4473
4540
|
storedAmountFloor,
|
|
@@ -4477,6 +4544,7 @@ function createDummyReservation2(userId, operationType) {
|
|
|
4477
4544
|
supportsIdempotentCredit,
|
|
4478
4545
|
synchronousDeferred,
|
|
4479
4546
|
terminalStatusOf,
|
|
4547
|
+
tierChangeJournalInput,
|
|
4480
4548
|
tierSchema,
|
|
4481
4549
|
toCents,
|
|
4482
4550
|
toClientUserCredits,
|