@sudobility/ratelimit_service 1.0.1
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/CLAUDE.md +160 -0
- package/dist/helpers/EntitlementHelper.cjs +75 -0
- package/dist/helpers/EntitlementHelper.d.ts +52 -0
- package/dist/helpers/EntitlementHelper.d.ts.map +1 -0
- package/dist/helpers/EntitlementHelper.js +75 -0
- package/dist/helpers/EntitlementHelper.js.map +1 -0
- package/dist/helpers/RateLimitChecker.cjs +264 -0
- package/dist/helpers/RateLimitChecker.d.ts +90 -0
- package/dist/helpers/RateLimitChecker.d.ts.map +1 -0
- package/dist/helpers/RateLimitChecker.js +264 -0
- package/dist/helpers/RateLimitChecker.js.map +1 -0
- package/dist/helpers/RateLimitRouteHandler.cjs +191 -0
- package/dist/helpers/RateLimitRouteHandler.d.ts +70 -0
- package/dist/helpers/RateLimitRouteHandler.d.ts.map +1 -0
- package/dist/helpers/RateLimitRouteHandler.js +191 -0
- package/dist/helpers/RateLimitRouteHandler.js.map +1 -0
- package/dist/helpers/RevenueCatHelper.cjs +96 -0
- package/dist/helpers/RevenueCatHelper.d.ts +51 -0
- package/dist/helpers/RevenueCatHelper.d.ts.map +1 -0
- package/dist/helpers/RevenueCatHelper.js +96 -0
- package/dist/helpers/RevenueCatHelper.js.map +1 -0
- package/dist/helpers/index.cjs +10 -0
- package/dist/helpers/index.d.ts +4 -0
- package/dist/helpers/index.d.ts.map +1 -0
- package/dist/helpers/index.js +10 -0
- package/dist/helpers/index.js.map +1 -0
- package/dist/index.cjs +36 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +36 -0
- package/dist/index.js.map +1 -0
- package/dist/middleware/hono.cjs +94 -0
- package/dist/middleware/hono.d.ts +63 -0
- package/dist/middleware/hono.d.ts.map +1 -0
- package/dist/middleware/hono.js +94 -0
- package/dist/middleware/hono.js.map +1 -0
- package/dist/schema/rate-limits.cjs +136 -0
- package/dist/schema/rate-limits.d.ts +333 -0
- package/dist/schema/rate-limits.d.ts.map +1 -0
- package/dist/schema/rate-limits.js +136 -0
- package/dist/schema/rate-limits.js.map +1 -0
- package/dist/types/entitlements.cjs +9 -0
- package/dist/types/entitlements.d.ts +29 -0
- package/dist/types/entitlements.d.ts.map +1 -0
- package/dist/types/entitlements.js +9 -0
- package/dist/types/entitlements.js.map +1 -0
- package/dist/types/index.cjs +20 -0
- package/dist/types/index.d.ts +4 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +20 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/rate-limits.cjs +3 -0
- package/dist/types/rate-limits.d.ts +34 -0
- package/dist/types/rate-limits.d.ts.map +1 -0
- package/dist/types/rate-limits.js +3 -0
- package/dist/types/rate-limits.js.map +1 -0
- package/dist/types/responses.cjs +13 -0
- package/dist/types/responses.d.ts +85 -0
- package/dist/types/responses.d.ts.map +1 -0
- package/dist/types/responses.js +13 -0
- package/dist/types/responses.js.map +1 -0
- package/dist/utils/time.cjs +180 -0
- package/dist/utils/time.d.ts +80 -0
- package/dist/utils/time.d.ts.map +1 -0
- package/dist/utils/time.js +180 -0
- package/dist/utils/time.js.map +1 -0
- package/package.json +79 -0
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import type { PostgresJsDatabase } from "drizzle-orm/postgres-js";
|
|
2
|
+
import type { PgTable, TableConfig } from "drizzle-orm/pg-core";
|
|
3
|
+
import { PeriodType, type RateLimits, type RateLimitCheckResult, type UsageHistory } from "../types";
|
|
4
|
+
/**
|
|
5
|
+
* Configuration for RateLimitChecker.
|
|
6
|
+
*/
|
|
7
|
+
export interface RateLimitCheckerConfig {
|
|
8
|
+
/** Drizzle database instance */
|
|
9
|
+
db: PostgresJsDatabase<any>;
|
|
10
|
+
/** The rate_limit_counters table from your schema */
|
|
11
|
+
table: PgTable<TableConfig>;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Checks and updates rate limits for a user.
|
|
15
|
+
* Uses period-based counters with history preservation.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```typescript
|
|
19
|
+
* import { db, rateLimitCounters } from "./db";
|
|
20
|
+
*
|
|
21
|
+
* const checker = new RateLimitChecker({ db, table: rateLimitCounters });
|
|
22
|
+
*
|
|
23
|
+
* const result = await checker.checkAndIncrement(
|
|
24
|
+
* userId,
|
|
25
|
+
* { hourly: 10, daily: 100, monthly: undefined },
|
|
26
|
+
* subscriptionStartedAt // from RevenueCat
|
|
27
|
+
* );
|
|
28
|
+
*
|
|
29
|
+
* if (!result.allowed) {
|
|
30
|
+
* return c.json({ error: "Rate limit exceeded" }, 429);
|
|
31
|
+
* }
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
export declare class RateLimitChecker {
|
|
35
|
+
private readonly db;
|
|
36
|
+
private readonly table;
|
|
37
|
+
constructor(config: RateLimitCheckerConfig);
|
|
38
|
+
/**
|
|
39
|
+
* Check if request is within rate limits and increment counters.
|
|
40
|
+
*
|
|
41
|
+
* @param userId - The user's ID
|
|
42
|
+
* @param limits - The rate limits to apply
|
|
43
|
+
* @param subscriptionStartedAt - When the subscription started (for monthly calculation)
|
|
44
|
+
* @returns Result indicating if request is allowed and remaining limits
|
|
45
|
+
*/
|
|
46
|
+
checkAndIncrement(userId: string, limits: RateLimits, subscriptionStartedAt?: Date | null): Promise<RateLimitCheckResult>;
|
|
47
|
+
/**
|
|
48
|
+
* Get current usage without incrementing (for status queries).
|
|
49
|
+
*/
|
|
50
|
+
checkOnly(userId: string, limits: RateLimits, subscriptionStartedAt?: Date | null): Promise<RateLimitCheckResult>;
|
|
51
|
+
/**
|
|
52
|
+
* Get usage history for a user.
|
|
53
|
+
*
|
|
54
|
+
* @param userId - The user's ID
|
|
55
|
+
* @param periodType - The period type to get history for
|
|
56
|
+
* @param subscriptionStartedAt - When the subscription started (for calculating period_end)
|
|
57
|
+
* @param limit - Maximum number of entries to return (default: 100)
|
|
58
|
+
* @returns Usage history with period start/end and counts
|
|
59
|
+
*/
|
|
60
|
+
getHistory(userId: string, periodType: PeriodType, subscriptionStartedAt?: Date | null, limit?: number): Promise<UsageHistory>;
|
|
61
|
+
/**
|
|
62
|
+
* Get current counts for each period type.
|
|
63
|
+
*/
|
|
64
|
+
private getCurrentCounts;
|
|
65
|
+
/**
|
|
66
|
+
* Get the counter value for a specific period.
|
|
67
|
+
*/
|
|
68
|
+
private getCountForPeriod;
|
|
69
|
+
/**
|
|
70
|
+
* Increment counters for enabled limit types.
|
|
71
|
+
*/
|
|
72
|
+
private incrementCounters;
|
|
73
|
+
/**
|
|
74
|
+
* Increment a specific period counter (upsert).
|
|
75
|
+
*/
|
|
76
|
+
private incrementPeriodCounter;
|
|
77
|
+
/**
|
|
78
|
+
* Check limits and return result.
|
|
79
|
+
*/
|
|
80
|
+
private checkLimits;
|
|
81
|
+
/**
|
|
82
|
+
* Calculate remaining requests for each period.
|
|
83
|
+
*/
|
|
84
|
+
private calculateRemaining;
|
|
85
|
+
/**
|
|
86
|
+
* Get the end of a period for history entries.
|
|
87
|
+
*/
|
|
88
|
+
private getPeriodEnd;
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=RateLimitChecker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RateLimitChecker.d.ts","sourceRoot":"","sources":["../../src/helpers/RateLimitChecker.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAClE,OAAO,KAAK,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAChE,OAAO,EACL,UAAU,EACV,KAAK,UAAU,EACf,KAAK,oBAAoB,EAGzB,KAAK,YAAY,EAElB,MAAM,UAAU,CAAC;AAUlB;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC,gCAAgC;IAChC,EAAE,EAAE,kBAAkB,CAAC,GAAG,CAAC,CAAC;IAC5B,qDAAqD;IACrD,KAAK,EAAE,OAAO,CAAC,WAAW,CAAC,CAAC;CAC7B;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAC,EAAE,CAA0B;IAC7C,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAuB;gBAEjC,MAAM,EAAE,sBAAsB;IAK1C;;;;;;;OAOG;IACG,iBAAiB,CACrB,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,UAAU,EAClB,qBAAqB,GAAE,IAAI,GAAG,IAAW,GACxC,OAAO,CAAC,oBAAoB,CAAC;IAsChC;;OAEG;IACG,SAAS,CACb,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,UAAU,EAClB,qBAAqB,GAAE,IAAI,GAAG,IAAW,GACxC,OAAO,CAAC,oBAAoB,CAAC;IAiBhC;;;;;;;;OAQG;IACG,UAAU,CACd,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,UAAU,EACtB,qBAAqB,GAAE,IAAI,GAAG,IAAW,EACzC,KAAK,GAAE,MAAY,GAClB,OAAO,CAAC,YAAY,CAAC;IAgCxB;;OAEG;YACW,gBAAgB;IA0B9B;;OAEG;YACW,iBAAiB;IA2B/B;;OAEG;YACW,iBAAiB;IA4C/B;;OAEG;YACW,sBAAsB;IA4CpC;;OAEG;IACH,OAAO,CAAC,WAAW;IA+CnB;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAoB1B;;OAEG;IACH,OAAO,CAAC,YAAY;CAiBrB"}
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RateLimitChecker = void 0;
|
|
4
|
+
const drizzle_orm_1 = require("drizzle-orm");
|
|
5
|
+
const types_1 = require("../types");
|
|
6
|
+
const time_1 = require("../utils/time");
|
|
7
|
+
/**
|
|
8
|
+
* Checks and updates rate limits for a user.
|
|
9
|
+
* Uses period-based counters with history preservation.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```typescript
|
|
13
|
+
* import { db, rateLimitCounters } from "./db";
|
|
14
|
+
*
|
|
15
|
+
* const checker = new RateLimitChecker({ db, table: rateLimitCounters });
|
|
16
|
+
*
|
|
17
|
+
* const result = await checker.checkAndIncrement(
|
|
18
|
+
* userId,
|
|
19
|
+
* { hourly: 10, daily: 100, monthly: undefined },
|
|
20
|
+
* subscriptionStartedAt // from RevenueCat
|
|
21
|
+
* );
|
|
22
|
+
*
|
|
23
|
+
* if (!result.allowed) {
|
|
24
|
+
* return c.json({ error: "Rate limit exceeded" }, 429);
|
|
25
|
+
* }
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
class RateLimitChecker {
|
|
29
|
+
constructor(config) {
|
|
30
|
+
this.db = config.db;
|
|
31
|
+
this.table = config.table;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Check if request is within rate limits and increment counters.
|
|
35
|
+
*
|
|
36
|
+
* @param userId - The user's ID
|
|
37
|
+
* @param limits - The rate limits to apply
|
|
38
|
+
* @param subscriptionStartedAt - When the subscription started (for monthly calculation)
|
|
39
|
+
* @returns Result indicating if request is allowed and remaining limits
|
|
40
|
+
*/
|
|
41
|
+
async checkAndIncrement(userId, limits, subscriptionStartedAt = null) {
|
|
42
|
+
const now = new Date();
|
|
43
|
+
// Get current counts for each period type
|
|
44
|
+
const counts = await this.getCurrentCounts(userId, subscriptionStartedAt, now);
|
|
45
|
+
// Check limits before incrementing
|
|
46
|
+
const checkResult = this.checkLimits(counts, limits);
|
|
47
|
+
if (!checkResult.allowed) {
|
|
48
|
+
return checkResult;
|
|
49
|
+
}
|
|
50
|
+
// Increment counters for enabled limit types
|
|
51
|
+
await this.incrementCounters(userId, limits, subscriptionStartedAt, now);
|
|
52
|
+
// Calculate remaining after increment
|
|
53
|
+
const remaining = this.calculateRemaining({
|
|
54
|
+
hourly: counts.hourly + (limits.hourly !== undefined ? 1 : 0),
|
|
55
|
+
daily: counts.daily + (limits.daily !== undefined ? 1 : 0),
|
|
56
|
+
monthly: counts.monthly + (limits.monthly !== undefined ? 1 : 0),
|
|
57
|
+
}, limits);
|
|
58
|
+
return {
|
|
59
|
+
allowed: true,
|
|
60
|
+
statusCode: 200,
|
|
61
|
+
remaining,
|
|
62
|
+
limits,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Get current usage without incrementing (for status queries).
|
|
67
|
+
*/
|
|
68
|
+
async checkOnly(userId, limits, subscriptionStartedAt = null) {
|
|
69
|
+
const now = new Date();
|
|
70
|
+
const counts = await this.getCurrentCounts(userId, subscriptionStartedAt, now);
|
|
71
|
+
const checkResult = this.checkLimits(counts, limits);
|
|
72
|
+
const remaining = this.calculateRemaining(counts, limits);
|
|
73
|
+
return {
|
|
74
|
+
...checkResult,
|
|
75
|
+
remaining,
|
|
76
|
+
limits,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Get usage history for a user.
|
|
81
|
+
*
|
|
82
|
+
* @param userId - The user's ID
|
|
83
|
+
* @param periodType - The period type to get history for
|
|
84
|
+
* @param subscriptionStartedAt - When the subscription started (for calculating period_end)
|
|
85
|
+
* @param limit - Maximum number of entries to return (default: 100)
|
|
86
|
+
* @returns Usage history with period start/end and counts
|
|
87
|
+
*/
|
|
88
|
+
async getHistory(userId, periodType, subscriptionStartedAt = null, limit = 100) {
|
|
89
|
+
const tableAny = this.table;
|
|
90
|
+
const rows = await this.db
|
|
91
|
+
.select()
|
|
92
|
+
.from(this.table)
|
|
93
|
+
.where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(tableAny.user_id, userId), (0, drizzle_orm_1.eq)(tableAny.period_type, periodType)))
|
|
94
|
+
.orderBy((0, drizzle_orm_1.desc)(tableAny.period_start))
|
|
95
|
+
.limit(limit);
|
|
96
|
+
const entries = rows.map(row => {
|
|
97
|
+
const counter = row;
|
|
98
|
+
return {
|
|
99
|
+
period_start: counter.period_start,
|
|
100
|
+
period_end: this.getPeriodEnd(periodType, counter.period_start, subscriptionStartedAt),
|
|
101
|
+
request_count: counter.request_count,
|
|
102
|
+
};
|
|
103
|
+
});
|
|
104
|
+
return {
|
|
105
|
+
user_id: userId,
|
|
106
|
+
period_type: periodType,
|
|
107
|
+
entries,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Get current counts for each period type.
|
|
112
|
+
*/
|
|
113
|
+
async getCurrentCounts(userId, subscriptionStartedAt, now) {
|
|
114
|
+
const [hourlyCount, dailyCount, monthlyCount] = await Promise.all([
|
|
115
|
+
this.getCountForPeriod(userId, types_1.PeriodType.HOURLY, (0, time_1.getCurrentHourStart)(now)),
|
|
116
|
+
this.getCountForPeriod(userId, types_1.PeriodType.DAILY, (0, time_1.getCurrentDayStart)(now)),
|
|
117
|
+
this.getCountForPeriod(userId, types_1.PeriodType.MONTHLY, (0, time_1.getSubscriptionMonthStart)(subscriptionStartedAt, now)),
|
|
118
|
+
]);
|
|
119
|
+
return {
|
|
120
|
+
hourly: hourlyCount,
|
|
121
|
+
daily: dailyCount,
|
|
122
|
+
monthly: monthlyCount,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Get the counter value for a specific period.
|
|
127
|
+
*/
|
|
128
|
+
async getCountForPeriod(userId, periodType, periodStart) {
|
|
129
|
+
const tableAny = this.table;
|
|
130
|
+
const rows = await this.db
|
|
131
|
+
.select()
|
|
132
|
+
.from(this.table)
|
|
133
|
+
.where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(tableAny.user_id, userId), (0, drizzle_orm_1.eq)(tableAny.period_type, periodType), (0, drizzle_orm_1.eq)(tableAny.period_start, periodStart)))
|
|
134
|
+
.limit(1);
|
|
135
|
+
if (rows.length === 0) {
|
|
136
|
+
return 0;
|
|
137
|
+
}
|
|
138
|
+
const counter = rows[0];
|
|
139
|
+
return counter.request_count;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Increment counters for enabled limit types.
|
|
143
|
+
*/
|
|
144
|
+
async incrementCounters(userId, limits, subscriptionStartedAt, now) {
|
|
145
|
+
const updates = [];
|
|
146
|
+
if (limits.hourly !== undefined) {
|
|
147
|
+
updates.push(this.incrementPeriodCounter(userId, types_1.PeriodType.HOURLY, (0, time_1.getCurrentHourStart)(now), now));
|
|
148
|
+
}
|
|
149
|
+
if (limits.daily !== undefined) {
|
|
150
|
+
updates.push(this.incrementPeriodCounter(userId, types_1.PeriodType.DAILY, (0, time_1.getCurrentDayStart)(now), now));
|
|
151
|
+
}
|
|
152
|
+
if (limits.monthly !== undefined) {
|
|
153
|
+
updates.push(this.incrementPeriodCounter(userId, types_1.PeriodType.MONTHLY, (0, time_1.getSubscriptionMonthStart)(subscriptionStartedAt, now), now));
|
|
154
|
+
}
|
|
155
|
+
await Promise.all(updates);
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Increment a specific period counter (upsert).
|
|
159
|
+
*/
|
|
160
|
+
async incrementPeriodCounter(userId, periodType, periodStart, now) {
|
|
161
|
+
const tableAny = this.table;
|
|
162
|
+
// Try to find existing counter
|
|
163
|
+
const existing = await this.db
|
|
164
|
+
.select()
|
|
165
|
+
.from(this.table)
|
|
166
|
+
.where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(tableAny.user_id, userId), (0, drizzle_orm_1.eq)(tableAny.period_type, periodType), (0, drizzle_orm_1.eq)(tableAny.period_start, periodStart)))
|
|
167
|
+
.limit(1);
|
|
168
|
+
if (existing.length > 0) {
|
|
169
|
+
// Update existing counter
|
|
170
|
+
const counter = existing[0];
|
|
171
|
+
await this.db
|
|
172
|
+
.update(this.table)
|
|
173
|
+
.set({
|
|
174
|
+
request_count: counter.request_count + 1,
|
|
175
|
+
updated_at: now,
|
|
176
|
+
})
|
|
177
|
+
.where((0, drizzle_orm_1.eq)(tableAny.id, counter.id));
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
// Insert new counter
|
|
181
|
+
await this.db.insert(this.table).values({
|
|
182
|
+
user_id: userId,
|
|
183
|
+
period_type: periodType,
|
|
184
|
+
period_start: periodStart,
|
|
185
|
+
request_count: 1,
|
|
186
|
+
created_at: now,
|
|
187
|
+
updated_at: now,
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Check limits and return result.
|
|
193
|
+
*/
|
|
194
|
+
checkLimits(counts, limits) {
|
|
195
|
+
const remaining = this.calculateRemaining(counts, limits);
|
|
196
|
+
// Check hourly limit
|
|
197
|
+
if (limits.hourly !== undefined && counts.hourly >= limits.hourly) {
|
|
198
|
+
return {
|
|
199
|
+
allowed: false,
|
|
200
|
+
statusCode: 429,
|
|
201
|
+
remaining,
|
|
202
|
+
exceededLimit: "hourly",
|
|
203
|
+
limits,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
// Check daily limit
|
|
207
|
+
if (limits.daily !== undefined && counts.daily >= limits.daily) {
|
|
208
|
+
return {
|
|
209
|
+
allowed: false,
|
|
210
|
+
statusCode: 429,
|
|
211
|
+
remaining,
|
|
212
|
+
exceededLimit: "daily",
|
|
213
|
+
limits,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
// Check monthly limit
|
|
217
|
+
if (limits.monthly !== undefined && counts.monthly >= limits.monthly) {
|
|
218
|
+
return {
|
|
219
|
+
allowed: false,
|
|
220
|
+
statusCode: 429,
|
|
221
|
+
remaining,
|
|
222
|
+
exceededLimit: "monthly",
|
|
223
|
+
limits,
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
return {
|
|
227
|
+
allowed: true,
|
|
228
|
+
statusCode: 200,
|
|
229
|
+
remaining,
|
|
230
|
+
limits,
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Calculate remaining requests for each period.
|
|
235
|
+
*/
|
|
236
|
+
calculateRemaining(counts, limits) {
|
|
237
|
+
return {
|
|
238
|
+
hourly: limits.hourly !== undefined
|
|
239
|
+
? Math.max(0, limits.hourly - counts.hourly)
|
|
240
|
+
: undefined,
|
|
241
|
+
daily: limits.daily !== undefined
|
|
242
|
+
? Math.max(0, limits.daily - counts.daily)
|
|
243
|
+
: undefined,
|
|
244
|
+
monthly: limits.monthly !== undefined
|
|
245
|
+
? Math.max(0, limits.monthly - counts.monthly)
|
|
246
|
+
: undefined,
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Get the end of a period for history entries.
|
|
251
|
+
*/
|
|
252
|
+
getPeriodEnd(periodType, periodStart, subscriptionStartedAt) {
|
|
253
|
+
switch (periodType) {
|
|
254
|
+
case types_1.PeriodType.HOURLY:
|
|
255
|
+
return (0, time_1.getNextHourStart)(periodStart);
|
|
256
|
+
case types_1.PeriodType.DAILY:
|
|
257
|
+
return (0, time_1.getNextDayStart)(periodStart);
|
|
258
|
+
case types_1.PeriodType.MONTHLY:
|
|
259
|
+
return (0, time_1.getNextSubscriptionMonthStart)(subscriptionStartedAt, periodStart);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
exports.RateLimitChecker = RateLimitChecker;
|
|
264
|
+
//# sourceMappingURL=RateLimitChecker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RateLimitChecker.js","sourceRoot":"","sources":["../../src/helpers/RateLimitChecker.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RateLimitRouteHandler = void 0;
|
|
4
|
+
const RevenueCatHelper_1 = require("./RevenueCatHelper");
|
|
5
|
+
const EntitlementHelper_1 = require("./EntitlementHelper");
|
|
6
|
+
const RateLimitChecker_1 = require("./RateLimitChecker");
|
|
7
|
+
const types_1 = require("../types");
|
|
8
|
+
/**
|
|
9
|
+
* Default display names for common entitlements.
|
|
10
|
+
*/
|
|
11
|
+
const DEFAULT_DISPLAY_NAMES = {
|
|
12
|
+
none: "Free",
|
|
13
|
+
starter: "Starter",
|
|
14
|
+
pro: "Pro",
|
|
15
|
+
enterprise: "Enterprise",
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Helper for rate limit API endpoints.
|
|
19
|
+
* Provides data for /ratelimits and /ratelimits/history endpoints.
|
|
20
|
+
*/
|
|
21
|
+
class RateLimitRouteHandler {
|
|
22
|
+
constructor(config) {
|
|
23
|
+
this.rcHelper = new RevenueCatHelper_1.RevenueCatHelper({ apiKey: config.revenueCatApiKey });
|
|
24
|
+
this.entitlementHelper = new EntitlementHelper_1.EntitlementHelper(config.rateLimitsConfig);
|
|
25
|
+
this.rateLimitChecker = new RateLimitChecker_1.RateLimitChecker({
|
|
26
|
+
db: config.db,
|
|
27
|
+
table: config.rateLimitsTable,
|
|
28
|
+
});
|
|
29
|
+
this.rateLimitsConfig = config.rateLimitsConfig;
|
|
30
|
+
this.displayNames = {
|
|
31
|
+
...DEFAULT_DISPLAY_NAMES,
|
|
32
|
+
...config.entitlementDisplayNames,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Get rate limits configuration data for /ratelimits endpoint.
|
|
37
|
+
*
|
|
38
|
+
* @param userId - The user's ID (e.g., Firebase UID)
|
|
39
|
+
* @returns RateLimitsConfigData for API response
|
|
40
|
+
*/
|
|
41
|
+
async getRateLimitsConfigData(userId) {
|
|
42
|
+
// Get all tiers from config
|
|
43
|
+
const tiers = Object.entries(this.rateLimitsConfig).map(([entitlement, limits]) => ({
|
|
44
|
+
entitlement,
|
|
45
|
+
displayName: this.getDisplayName(entitlement),
|
|
46
|
+
limits: this.convertLimits(limits),
|
|
47
|
+
}));
|
|
48
|
+
// Get user's subscription info from RevenueCat
|
|
49
|
+
let entitlements;
|
|
50
|
+
let subscriptionStartedAt = null;
|
|
51
|
+
try {
|
|
52
|
+
const subscriptionInfo = await this.rcHelper.getSubscriptionInfo(userId);
|
|
53
|
+
entitlements = subscriptionInfo.entitlements;
|
|
54
|
+
subscriptionStartedAt = subscriptionInfo.subscriptionStartedAt;
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
console.error("RevenueCat error, using 'none' entitlement:", error);
|
|
58
|
+
entitlements = [types_1.NONE_ENTITLEMENT];
|
|
59
|
+
}
|
|
60
|
+
// Get the primary entitlement (first one that has limits configured)
|
|
61
|
+
const currentEntitlement = this.getPrimaryEntitlement(entitlements);
|
|
62
|
+
// Get rate limits for user's entitlements
|
|
63
|
+
const internalLimits = this.entitlementHelper.getRateLimits(entitlements);
|
|
64
|
+
const currentLimits = this.convertLimits(internalLimits);
|
|
65
|
+
// Get current usage
|
|
66
|
+
const checkResult = await this.rateLimitChecker.checkOnly(userId, internalLimits, subscriptionStartedAt);
|
|
67
|
+
const currentUsage = {
|
|
68
|
+
hourly: internalLimits.hourly !== undefined
|
|
69
|
+
? internalLimits.hourly - (checkResult.remaining.hourly ?? 0)
|
|
70
|
+
: 0,
|
|
71
|
+
daily: internalLimits.daily !== undefined
|
|
72
|
+
? internalLimits.daily - (checkResult.remaining.daily ?? 0)
|
|
73
|
+
: 0,
|
|
74
|
+
monthly: internalLimits.monthly !== undefined
|
|
75
|
+
? internalLimits.monthly - (checkResult.remaining.monthly ?? 0)
|
|
76
|
+
: 0,
|
|
77
|
+
};
|
|
78
|
+
return {
|
|
79
|
+
tiers,
|
|
80
|
+
currentEntitlement,
|
|
81
|
+
currentLimits,
|
|
82
|
+
currentUsage,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Get rate limit history data for /ratelimits/history/{periodType} endpoint.
|
|
87
|
+
*
|
|
88
|
+
* @param userId - The user's ID (e.g., Firebase UID)
|
|
89
|
+
* @param periodType - The period type (hour, day, month)
|
|
90
|
+
* @param limit - Maximum number of entries to return (default: 100)
|
|
91
|
+
* @returns RateLimitHistoryData for API response
|
|
92
|
+
*/
|
|
93
|
+
async getRateLimitHistoryData(userId, periodType, limit = 100) {
|
|
94
|
+
// Convert API period type to internal period type
|
|
95
|
+
const internalPeriodType = this.convertPeriodType(periodType);
|
|
96
|
+
// Get user's subscription info for subscription month calculation
|
|
97
|
+
let subscriptionStartedAt = null;
|
|
98
|
+
let entitlements = [types_1.NONE_ENTITLEMENT];
|
|
99
|
+
try {
|
|
100
|
+
const subscriptionInfo = await this.rcHelper.getSubscriptionInfo(userId);
|
|
101
|
+
subscriptionStartedAt = subscriptionInfo.subscriptionStartedAt;
|
|
102
|
+
entitlements = subscriptionInfo.entitlements;
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
console.error("RevenueCat error:", error);
|
|
106
|
+
}
|
|
107
|
+
// Get the limit for this period type
|
|
108
|
+
const internalLimits = this.entitlementHelper.getRateLimits(entitlements);
|
|
109
|
+
const periodLimit = this.getLimitForPeriod(internalLimits, periodType);
|
|
110
|
+
// Get history from database
|
|
111
|
+
const history = await this.rateLimitChecker.getHistory(userId, internalPeriodType, subscriptionStartedAt, limit);
|
|
112
|
+
// Convert to API format
|
|
113
|
+
const entries = history.entries.map(entry => ({
|
|
114
|
+
periodStart: entry.period_start.toISOString(),
|
|
115
|
+
periodEnd: entry.period_end.toISOString(),
|
|
116
|
+
requestCount: entry.request_count,
|
|
117
|
+
limit: periodLimit,
|
|
118
|
+
}));
|
|
119
|
+
return {
|
|
120
|
+
periodType,
|
|
121
|
+
entries,
|
|
122
|
+
totalEntries: entries.length,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Get display name for an entitlement.
|
|
127
|
+
*/
|
|
128
|
+
getDisplayName(entitlement) {
|
|
129
|
+
if (this.displayNames[entitlement]) {
|
|
130
|
+
return this.displayNames[entitlement];
|
|
131
|
+
}
|
|
132
|
+
// Capitalize first letter as fallback
|
|
133
|
+
return entitlement.charAt(0).toUpperCase() + entitlement.slice(1);
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Get the primary entitlement from a list.
|
|
137
|
+
* Returns the first entitlement that has configured limits, or "none".
|
|
138
|
+
*/
|
|
139
|
+
getPrimaryEntitlement(entitlements) {
|
|
140
|
+
for (const entitlement of entitlements) {
|
|
141
|
+
if (entitlement !== types_1.NONE_ENTITLEMENT &&
|
|
142
|
+
this.rateLimitsConfig[entitlement]) {
|
|
143
|
+
return entitlement;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return types_1.NONE_ENTITLEMENT;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Convert internal RateLimits to API RateLimits.
|
|
150
|
+
* Internal uses undefined for unlimited, API uses null.
|
|
151
|
+
*/
|
|
152
|
+
convertLimits(limits) {
|
|
153
|
+
return {
|
|
154
|
+
hourly: limits.hourly ?? null,
|
|
155
|
+
daily: limits.daily ?? null,
|
|
156
|
+
monthly: limits.monthly ?? null,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Convert API period type to internal period type.
|
|
161
|
+
*/
|
|
162
|
+
convertPeriodType(periodType) {
|
|
163
|
+
switch (periodType) {
|
|
164
|
+
case "hour":
|
|
165
|
+
return types_1.PeriodType.HOURLY;
|
|
166
|
+
case "day":
|
|
167
|
+
return types_1.PeriodType.DAILY;
|
|
168
|
+
case "month":
|
|
169
|
+
return types_1.PeriodType.MONTHLY;
|
|
170
|
+
default:
|
|
171
|
+
throw new Error(`Invalid period type: ${periodType}`);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Get the limit for a specific period type.
|
|
176
|
+
*/
|
|
177
|
+
getLimitForPeriod(limits, periodType) {
|
|
178
|
+
switch (periodType) {
|
|
179
|
+
case "hour":
|
|
180
|
+
return limits.hourly ?? null;
|
|
181
|
+
case "day":
|
|
182
|
+
return limits.daily ?? null;
|
|
183
|
+
case "month":
|
|
184
|
+
return limits.monthly ?? null;
|
|
185
|
+
default:
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
exports.RateLimitRouteHandler = RateLimitRouteHandler;
|
|
191
|
+
//# sourceMappingURL=RateLimitRouteHandler.js.map
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { PostgresJsDatabase } from "drizzle-orm/postgres-js";
|
|
2
|
+
import type { PgTable, TableConfig } from "drizzle-orm/pg-core";
|
|
3
|
+
import type { RateLimitsConfigData, RateLimitHistoryData, RateLimitPeriodType } from "@sudobility/types";
|
|
4
|
+
import { type RateLimitsConfig } from "../types";
|
|
5
|
+
/**
|
|
6
|
+
* Configuration for RateLimitRouteHandler.
|
|
7
|
+
*/
|
|
8
|
+
export interface RateLimitRouteHandlerConfig {
|
|
9
|
+
/** RevenueCat API key */
|
|
10
|
+
revenueCatApiKey: string;
|
|
11
|
+
/** Rate limits configuration */
|
|
12
|
+
rateLimitsConfig: RateLimitsConfig;
|
|
13
|
+
/** Drizzle database instance */
|
|
14
|
+
db: PostgresJsDatabase<any>;
|
|
15
|
+
/** The rate_limit_counters table from your schema */
|
|
16
|
+
rateLimitsTable: PgTable<TableConfig>;
|
|
17
|
+
/** Display name mapping for entitlements (optional) */
|
|
18
|
+
entitlementDisplayNames?: Record<string, string>;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Helper for rate limit API endpoints.
|
|
22
|
+
* Provides data for /ratelimits and /ratelimits/history endpoints.
|
|
23
|
+
*/
|
|
24
|
+
export declare class RateLimitRouteHandler {
|
|
25
|
+
private readonly rcHelper;
|
|
26
|
+
private readonly entitlementHelper;
|
|
27
|
+
private readonly rateLimitChecker;
|
|
28
|
+
private readonly rateLimitsConfig;
|
|
29
|
+
private readonly displayNames;
|
|
30
|
+
constructor(config: RateLimitRouteHandlerConfig);
|
|
31
|
+
/**
|
|
32
|
+
* Get rate limits configuration data for /ratelimits endpoint.
|
|
33
|
+
*
|
|
34
|
+
* @param userId - The user's ID (e.g., Firebase UID)
|
|
35
|
+
* @returns RateLimitsConfigData for API response
|
|
36
|
+
*/
|
|
37
|
+
getRateLimitsConfigData(userId: string): Promise<RateLimitsConfigData>;
|
|
38
|
+
/**
|
|
39
|
+
* Get rate limit history data for /ratelimits/history/{periodType} endpoint.
|
|
40
|
+
*
|
|
41
|
+
* @param userId - The user's ID (e.g., Firebase UID)
|
|
42
|
+
* @param periodType - The period type (hour, day, month)
|
|
43
|
+
* @param limit - Maximum number of entries to return (default: 100)
|
|
44
|
+
* @returns RateLimitHistoryData for API response
|
|
45
|
+
*/
|
|
46
|
+
getRateLimitHistoryData(userId: string, periodType: RateLimitPeriodType, limit?: number): Promise<RateLimitHistoryData>;
|
|
47
|
+
/**
|
|
48
|
+
* Get display name for an entitlement.
|
|
49
|
+
*/
|
|
50
|
+
private getDisplayName;
|
|
51
|
+
/**
|
|
52
|
+
* Get the primary entitlement from a list.
|
|
53
|
+
* Returns the first entitlement that has configured limits, or "none".
|
|
54
|
+
*/
|
|
55
|
+
private getPrimaryEntitlement;
|
|
56
|
+
/**
|
|
57
|
+
* Convert internal RateLimits to API RateLimits.
|
|
58
|
+
* Internal uses undefined for unlimited, API uses null.
|
|
59
|
+
*/
|
|
60
|
+
private convertLimits;
|
|
61
|
+
/**
|
|
62
|
+
* Convert API period type to internal period type.
|
|
63
|
+
*/
|
|
64
|
+
private convertPeriodType;
|
|
65
|
+
/**
|
|
66
|
+
* Get the limit for a specific period type.
|
|
67
|
+
*/
|
|
68
|
+
private getLimitForPeriod;
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=RateLimitRouteHandler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RateLimitRouteHandler.d.ts","sourceRoot":"","sources":["../../src/helpers/RateLimitRouteHandler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAClE,OAAO,KAAK,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAChE,OAAO,KAAK,EACV,oBAAoB,EAIpB,oBAAoB,EAEpB,mBAAmB,EACpB,MAAM,mBAAmB,CAAC;AAI3B,OAAO,EAGL,KAAK,gBAAgB,EAEtB,MAAM,UAAU,CAAC;AAElB;;GAEG;AACH,MAAM,WAAW,2BAA2B;IAC1C,yBAAyB;IACzB,gBAAgB,EAAE,MAAM,CAAC;IACzB,gCAAgC;IAChC,gBAAgB,EAAE,gBAAgB,CAAC;IACnC,gCAAgC;IAChC,EAAE,EAAE,kBAAkB,CAAC,GAAG,CAAC,CAAC;IAC5B,qDAAqD;IACrD,eAAe,EAAE,OAAO,CAAC,WAAW,CAAC,CAAC;IACtC,uDAAuD;IACvD,uBAAuB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClD;AAYD;;;GAGG;AACH,qBAAa,qBAAqB;IAChC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAmB;IAC5C,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAoB;IACtD,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAmB;IACpD,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAmB;IACpD,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAyB;gBAE1C,MAAM,EAAE,2BAA2B;IAc/C;;;;;OAKG;IACG,uBAAuB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,CAAC;IA2D5E;;;;;;;OAOG;IACG,uBAAuB,CAC3B,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,mBAAmB,EAC/B,KAAK,GAAE,MAAY,GAClB,OAAO,CAAC,oBAAoB,CAAC;IA0ChC;;OAEG;IACH,OAAO,CAAC,cAAc;IAQtB;;;OAGG;IACH,OAAO,CAAC,qBAAqB;IAY7B;;;OAGG;IACH,OAAO,CAAC,aAAa;IAQrB;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAazB;;OAEG;IACH,OAAO,CAAC,iBAAiB;CAe1B"}
|