@nxuss/lemma 0.5.5 → 0.5.7
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/cjs/cli/lemma-proxy.d.ts.map +1 -1
- package/dist/cjs/cli/lemma-proxy.js +23 -1
- package/dist/cjs/cli/lemma-proxy.js.map +1 -1
- package/dist/cjs/cloud/BurstRateLimiter.d.ts +26 -0
- package/dist/cjs/cloud/BurstRateLimiter.d.ts.map +1 -0
- package/dist/cjs/cloud/BurstRateLimiter.js +63 -0
- package/dist/cjs/cloud/BurstRateLimiter.js.map +1 -0
- package/dist/cjs/cloud/KeyManager.d.ts +1 -0
- package/dist/cjs/cloud/KeyManager.d.ts.map +1 -1
- package/dist/cjs/cloud/KeyManager.js +28 -1
- package/dist/cjs/cloud/KeyManager.js.map +1 -1
- package/dist/cjs/cloud/PostgresQuotaLimiter.d.ts +60 -0
- package/dist/cjs/cloud/PostgresQuotaLimiter.d.ts.map +1 -0
- package/dist/cjs/cloud/PostgresQuotaLimiter.js +200 -0
- package/dist/cjs/cloud/PostgresQuotaLimiter.js.map +1 -0
- package/dist/cjs/cloud/TenantCache.d.ts +1 -1
- package/dist/cjs/cloud/TenantCache.d.ts.map +1 -1
- package/dist/cjs/cloud/TenantCache.js +21 -3
- package/dist/cjs/cloud/TenantCache.js.map +1 -1
- package/dist/cjs/cloud/server.d.ts +3 -1
- package/dist/cjs/cloud/server.d.ts.map +1 -1
- package/dist/cjs/cloud/server.js +42 -5
- package/dist/cjs/cloud/server.js.map +1 -1
- package/dist/cjs/cloud/types.d.ts +3 -0
- package/dist/cjs/cloud/types.d.ts.map +1 -1
- package/dist/cjs/cloud/types.js +3 -3
- package/dist/cjs/cloud/types.js.map +1 -1
- package/dist/esm/cli/lemma-proxy.d.ts.map +1 -1
- package/dist/esm/cli/lemma-proxy.js +23 -1
- package/dist/esm/cli/lemma-proxy.js.map +1 -1
- package/dist/esm/cloud/BurstRateLimiter.d.ts +26 -0
- package/dist/esm/cloud/BurstRateLimiter.d.ts.map +1 -0
- package/dist/esm/cloud/BurstRateLimiter.js +59 -0
- package/dist/esm/cloud/BurstRateLimiter.js.map +1 -0
- package/dist/esm/cloud/KeyManager.d.ts +1 -0
- package/dist/esm/cloud/KeyManager.d.ts.map +1 -1
- package/dist/esm/cloud/KeyManager.js +28 -1
- package/dist/esm/cloud/KeyManager.js.map +1 -1
- package/dist/esm/cloud/PostgresQuotaLimiter.d.ts +60 -0
- package/dist/esm/cloud/PostgresQuotaLimiter.d.ts.map +1 -0
- package/dist/esm/cloud/PostgresQuotaLimiter.js +196 -0
- package/dist/esm/cloud/PostgresQuotaLimiter.js.map +1 -0
- package/dist/esm/cloud/TenantCache.d.ts +1 -1
- package/dist/esm/cloud/TenantCache.d.ts.map +1 -1
- package/dist/esm/cloud/TenantCache.js +21 -3
- package/dist/esm/cloud/TenantCache.js.map +1 -1
- package/dist/esm/cloud/server.d.ts +3 -1
- package/dist/esm/cloud/server.d.ts.map +1 -1
- package/dist/esm/cloud/server.js +41 -5
- package/dist/esm/cloud/server.js.map +1 -1
- package/dist/esm/cloud/types.d.ts +3 -0
- package/dist/esm/cloud/types.d.ts.map +1 -1
- package/dist/esm/cloud/types.js +3 -3
- package/dist/esm/cloud/types.js.map +1 -1
- package/package.json +2 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BurstRateLimiter.js","sourceRoot":"","sources":["../../../src/cloud/BurstRateLimiter.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,OAAO,gBAAgB;IAK3B,YAAY,MAAM,GAAG,EAAE,EAAE,YAAY,GAAG,IAAI;QAJpC,YAAO,GAAG,IAAI,GAAG,EAAoB,CAAC;QAK5C,IAAI,CAAC,oBAAoB,GAAG,MAAM,CAAC;QACnC,IAAI,CAAC,YAAY,GAAG,YAAY,CAAC;QAEjC,4DAA4D;QAC5D,WAAW,CAAC,GAAG,EAAE;YACf,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YACvB,MAAM,cAAc,GAAG,GAAG,GAAG,IAAI,CAAC,YAAY,CAAC;YAE/C,KAAK,MAAM,CAAC,GAAG,EAAE,UAAU,CAAC,IAAI,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC;gBACvD,MAAM,MAAM,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,cAAc,CAAC,CAAC;gBAC1D,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;oBACxB,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;gBAC3B,CAAC;qBAAM,CAAC;oBACN,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;gBAChC,CAAC;YACH,CAAC;QACH,CAAC,EAAE,KAAK,CAAC,CAAC,KAAK,EAAE,CAAC;IACpB,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,KAAa;QACjB,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,MAAM,cAAc,GAAG,GAAG,GAAG,IAAI,CAAC,YAAY,CAAC;QAE/C,IAAI,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;QAE/C,iDAAiD;QACjD,UAAU,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,cAAc,CAAC,CAAC;QAExD,IAAI,UAAU,CAAC,MAAM,IAAI,IAAI,CAAC,oBAAoB,EAAE,CAAC;YACnD,OAAO;gBACL,OAAO,EAAE,KAAK;gBACd,SAAS,EAAE,CAAC;aACb,CAAC;QACJ,CAAC;QAED,2BAA2B;QAC3B,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACrB,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC;QAEpC,OAAO;YACL,OAAO,EAAE,IAAI;YACb,SAAS,EAAE,IAAI,CAAC,oBAAoB,GAAG,UAAU,CAAC,MAAM;SACzD,CAAC;IACJ,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,KAAa;QACjB,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAC7B,CAAC;CACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"KeyManager.d.ts","sourceRoot":"","sources":["../../../src/cloud/KeyManager.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,OAAO,EAAE,MAAM,EAAe,MAAM,SAAS,CAAC;AAE9C,qBAAa,UAAU;IACrB,OAAO,CAAC,IAAI,CAA6B;IACzC,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,SAAS,CAA+B;gBAEpC,SAAS,SAAqB;IAO1C,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,GAAE,MAAM,CAAC,MAAM,CAAU,GAAG,MAAM;IAkB/E,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG;QAAE,KAAK,EAAE,OAAO,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE;
|
|
1
|
+
{"version":3,"file":"KeyManager.d.ts","sourceRoot":"","sources":["../../../src/cloud/KeyManager.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,OAAO,EAAE,MAAM,EAAe,MAAM,SAAS,CAAC;AAE9C,qBAAa,UAAU;IACrB,OAAO,CAAC,IAAI,CAA6B;IACzC,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,SAAS,CAA+B;gBAEpC,SAAS,SAAqB;IAO1C,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,GAAE,MAAM,CAAC,MAAM,CAAU,GAAG,MAAM;IAkB/E,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG;QAAE,KAAK,EAAE,OAAO,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE;IAsC1E,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAWlC,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,GAAG,OAAO;IAQzD,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO;IAQ9B,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE;IAIvC,GAAG,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAMtC,OAAO,CAAC,eAAe;IAUvB,OAAO,CAAC,iBAAiB;IAezB,OAAO,CAAC,YAAY;IAQpB,OAAO,CAAC,IAAI;IAWZ,OAAO,CAAC,IAAI;CAiBb"}
|
|
@@ -25,7 +25,7 @@ export class KeyManager {
|
|
|
25
25
|
plan,
|
|
26
26
|
createdAt: Date.now(),
|
|
27
27
|
active: true,
|
|
28
|
-
usage: { today: 0, total: 0, lastReset: Date.now() },
|
|
28
|
+
usage: { today: 0, monthly: 0, total: 0, lastReset: Date.now(), lastMonthlyReset: Date.now() },
|
|
29
29
|
};
|
|
30
30
|
this.keys.set(id, key);
|
|
31
31
|
this.scheduleSave();
|
|
@@ -43,6 +43,8 @@ export class KeyManager {
|
|
|
43
43
|
}
|
|
44
44
|
// Reset daily counter if needed
|
|
45
45
|
this.maybeResetDaily(key);
|
|
46
|
+
// Reset monthly counter if needed
|
|
47
|
+
this.maybeResetMonthly(key);
|
|
46
48
|
// Check plan limits
|
|
47
49
|
const limits = PLAN_LIMITS[key.plan];
|
|
48
50
|
if (key.usage.today >= limits.dailyRequests) {
|
|
@@ -53,6 +55,12 @@ export class KeyManager {
|
|
|
53
55
|
: 'Daily request limit reached. Contact support to increase limits.',
|
|
54
56
|
};
|
|
55
57
|
}
|
|
58
|
+
if (!process.env.DATABASE_URL && key.usage.monthly >= limits.monthlyRequests) {
|
|
59
|
+
return {
|
|
60
|
+
valid: false,
|
|
61
|
+
reason: 'Monthly Cloud Quota Exceeded. Please upgrade your plan.',
|
|
62
|
+
};
|
|
63
|
+
}
|
|
56
64
|
return { valid: true, key };
|
|
57
65
|
}
|
|
58
66
|
// ─── Usage Tracking ─────────────────────────────────────────────────────────
|
|
@@ -61,6 +69,7 @@ export class KeyManager {
|
|
|
61
69
|
if (!key)
|
|
62
70
|
return;
|
|
63
71
|
key.usage.today++;
|
|
72
|
+
key.usage.monthly++;
|
|
64
73
|
key.usage.total++;
|
|
65
74
|
this.scheduleSave();
|
|
66
75
|
}
|
|
@@ -97,6 +106,17 @@ export class KeyManager {
|
|
|
97
106
|
key.usage.lastReset = now;
|
|
98
107
|
}
|
|
99
108
|
}
|
|
109
|
+
maybeResetMonthly(key) {
|
|
110
|
+
const now = Date.now();
|
|
111
|
+
const currentDate = new Date(now);
|
|
112
|
+
const lastResetDate = new Date(key.usage.lastMonthlyReset || 0);
|
|
113
|
+
if (currentDate.getUTCMonth() !== lastResetDate.getUTCMonth() ||
|
|
114
|
+
currentDate.getUTCFullYear() !== lastResetDate.getUTCFullYear()) {
|
|
115
|
+
key.usage.monthly = 0;
|
|
116
|
+
key.usage.lastMonthlyReset = now;
|
|
117
|
+
this.scheduleSave();
|
|
118
|
+
}
|
|
119
|
+
}
|
|
100
120
|
scheduleSave() {
|
|
101
121
|
if (this.saveTimer)
|
|
102
122
|
return;
|
|
@@ -123,6 +143,13 @@ export class KeyManager {
|
|
|
123
143
|
return;
|
|
124
144
|
const data = JSON.parse(fs.readFileSync(this.storePath, 'utf-8'));
|
|
125
145
|
for (const [id, key] of data) {
|
|
146
|
+
// Upgrade legacy schema key attributes
|
|
147
|
+
if (key && key.usage) {
|
|
148
|
+
if (key.usage.monthly === undefined)
|
|
149
|
+
key.usage.monthly = 0;
|
|
150
|
+
if (key.usage.lastMonthlyReset === undefined)
|
|
151
|
+
key.usage.lastMonthlyReset = key.usage.lastReset || Date.now();
|
|
152
|
+
}
|
|
126
153
|
this.keys.set(id, key);
|
|
127
154
|
}
|
|
128
155
|
console.log(`[KeyManager] Loaded ${this.keys.size} keys`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"KeyManager.js","sourceRoot":"","sources":["../../../src/cloud/KeyManager.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,MAAM,MAAM,QAAQ,CAAC;AAC5B,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAU,WAAW,EAAE,MAAM,SAAS,CAAC;AAE9C,MAAM,OAAO,UAAU;IAKrB,YAAY,SAAS,GAAG,kBAAkB;QAJlC,SAAI,GAAG,IAAI,GAAG,EAAkB,CAAC;QAEjC,cAAS,GAA0B,IAAI,CAAC;QAG9C,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;QAC3B,IAAI,CAAC,IAAI,EAAE,CAAC;IACd,CAAC;IAED,+EAA+E;IAE/E,QAAQ,CAAC,QAAgB,EAAE,IAAY,EAAE,OAAuB,MAAM;QACpE,MAAM,EAAE,GAAG,MAAM,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,IAAI,MAAM,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QAC/F,MAAM,GAAG,GAAW;YAClB,EAAE;YACF,QAAQ;YACR,IAAI;YACJ,IAAI;YACJ,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;YACrB,MAAM,EAAE,IAAI;YACZ,KAAK,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE;
|
|
1
|
+
{"version":3,"file":"KeyManager.js","sourceRoot":"","sources":["../../../src/cloud/KeyManager.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { Pool } from 'pg';
|
|
2
|
+
export interface TenantQuota {
|
|
3
|
+
tenantId: string;
|
|
4
|
+
totalRequestsThisMonth: number;
|
|
5
|
+
quotaLimit: number;
|
|
6
|
+
billingCycleResetDate: Date;
|
|
7
|
+
}
|
|
8
|
+
export declare class PostgresQuotaLimiter {
|
|
9
|
+
private pool;
|
|
10
|
+
private inMemoryQuotas;
|
|
11
|
+
private pendingIncrements;
|
|
12
|
+
private flushIntervalMs;
|
|
13
|
+
private flushTimer;
|
|
14
|
+
private isFlushing;
|
|
15
|
+
constructor(pool: Pool, flushIntervalMs?: number);
|
|
16
|
+
/**
|
|
17
|
+
* Start the background flush timer
|
|
18
|
+
*/
|
|
19
|
+
start(): void;
|
|
20
|
+
/**
|
|
21
|
+
* Stop the background flush timer and flush any remaining increments
|
|
22
|
+
*/
|
|
23
|
+
stop(): Promise<void>;
|
|
24
|
+
/**
|
|
25
|
+
* Synchronously validate a tenant's request against in-memory limits.
|
|
26
|
+
* If a cache miss occurs, initializes with default limits and lazily loads from Postgres in the background.
|
|
27
|
+
*/
|
|
28
|
+
checkQuota(tenantId: string, defaultLimit?: number): {
|
|
29
|
+
allowed: boolean;
|
|
30
|
+
reason?: string;
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Lazily loads tenant quota details from PostgreSQL in the background.
|
|
34
|
+
* Merges loaded DB counts with any in-memory increments that accumulated in the meantime.
|
|
35
|
+
*/
|
|
36
|
+
private lazyLoadTenantQuota;
|
|
37
|
+
/**
|
|
38
|
+
* Queue increments in memory to await bulk batch flush
|
|
39
|
+
*/
|
|
40
|
+
private enqueueIncrement;
|
|
41
|
+
/**
|
|
42
|
+
* Performs an atomic database bulk UPSERT of all pending in-memory increments.
|
|
43
|
+
* If a database error occurs, pending increments are safely rolled back and merged
|
|
44
|
+
* into the in-memory map to try again in the next batch flush cycle.
|
|
45
|
+
*/
|
|
46
|
+
flushPendingIncrements(): Promise<void>;
|
|
47
|
+
/**
|
|
48
|
+
* Helper to calculate the 1st of the next calendar month at midnight UTC
|
|
49
|
+
*/
|
|
50
|
+
private calculateNextResetDate;
|
|
51
|
+
/**
|
|
52
|
+
* For test harness only: directly seed an in-memory quota entry
|
|
53
|
+
*/
|
|
54
|
+
seedQuotaForTest(tenantId: string, quota: TenantQuota): void;
|
|
55
|
+
/**
|
|
56
|
+
* For test harness only: check in-memory status
|
|
57
|
+
*/
|
|
58
|
+
getInMemoryQuota(tenantId: string): TenantQuota | undefined;
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=PostgresQuotaLimiter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PostgresQuotaLimiter.d.ts","sourceRoot":"","sources":["../../../src/cloud/PostgresQuotaLimiter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,IAAI,CAAC;AAE1B,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,sBAAsB,EAAE,MAAM,CAAC;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,qBAAqB,EAAE,IAAI,CAAC;CAC7B;AAED,qBAAa,oBAAoB;IAC/B,OAAO,CAAC,IAAI,CAAO;IACnB,OAAO,CAAC,cAAc,CAAkC;IACxD,OAAO,CAAC,iBAAiB,CAA6B;IACtD,OAAO,CAAC,eAAe,CAAS;IAChC,OAAO,CAAC,UAAU,CAA+B;IACjD,OAAO,CAAC,UAAU,CAAS;gBAEf,IAAI,EAAE,IAAI,EAAE,eAAe,SAAO;IAK9C;;OAEG;IACH,KAAK,IAAI,IAAI;IAUb;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAS3B;;;OAGG;IACH,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,YAAY,SAAQ,GAAG;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE;IA6CzF;;;OAGG;YACW,mBAAmB;IA8CjC;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAKxB;;;;OAIG;IACG,sBAAsB,IAAI,OAAO,CAAC,IAAI,CAAC;IAiD7C;;OAEG;IACH,OAAO,CAAC,sBAAsB;IAQ9B;;OAEG;IACH,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,GAAG,IAAI;IAI5D;;OAEG;IACH,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS;CAG5D"}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
export class PostgresQuotaLimiter {
|
|
2
|
+
constructor(pool, flushIntervalMs = 5000) {
|
|
3
|
+
this.inMemoryQuotas = new Map();
|
|
4
|
+
this.pendingIncrements = new Map();
|
|
5
|
+
this.flushTimer = null;
|
|
6
|
+
this.isFlushing = false;
|
|
7
|
+
this.pool = pool;
|
|
8
|
+
this.flushIntervalMs = flushIntervalMs;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Start the background flush timer
|
|
12
|
+
*/
|
|
13
|
+
start() {
|
|
14
|
+
if (this.flushTimer)
|
|
15
|
+
return;
|
|
16
|
+
this.flushTimer = setInterval(() => {
|
|
17
|
+
this.flushPendingIncrements().catch(err => {
|
|
18
|
+
console.error('[PostgresQuotaLimiter] Background flush failed:', err);
|
|
19
|
+
});
|
|
20
|
+
}, this.flushIntervalMs).unref();
|
|
21
|
+
console.log(`[PostgresQuotaLimiter] Asynchronous batch flusher started (interval: ${this.flushIntervalMs}ms)`);
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Stop the background flush timer and flush any remaining increments
|
|
25
|
+
*/
|
|
26
|
+
async stop() {
|
|
27
|
+
if (this.flushTimer) {
|
|
28
|
+
clearInterval(this.flushTimer);
|
|
29
|
+
this.flushTimer = null;
|
|
30
|
+
}
|
|
31
|
+
console.log('[PostgresQuotaLimiter] Stopping flusher, performing final flush...');
|
|
32
|
+
await this.flushPendingIncrements();
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Synchronously validate a tenant's request against in-memory limits.
|
|
36
|
+
* If a cache miss occurs, initializes with default limits and lazily loads from Postgres in the background.
|
|
37
|
+
*/
|
|
38
|
+
checkQuota(tenantId, defaultLimit = 50000) {
|
|
39
|
+
let quota = this.inMemoryQuotas.get(tenantId);
|
|
40
|
+
if (!quota) {
|
|
41
|
+
// 1. First-time seen in this memory session: initialize with sensible default
|
|
42
|
+
const nextReset = this.calculateNextResetDate();
|
|
43
|
+
quota = {
|
|
44
|
+
tenantId,
|
|
45
|
+
totalRequestsThisMonth: 0,
|
|
46
|
+
quotaLimit: defaultLimit,
|
|
47
|
+
billingCycleResetDate: nextReset,
|
|
48
|
+
};
|
|
49
|
+
this.inMemoryQuotas.set(tenantId, quota);
|
|
50
|
+
// 2. Trigger asynchronous, non-blocking load from PostgreSQL
|
|
51
|
+
this.lazyLoadTenantQuota(tenantId, defaultLimit).catch(err => {
|
|
52
|
+
console.error(`[PostgresQuotaLimiter] Failed to lazy load quota for ${tenantId}:`, err);
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
const now = Date.now();
|
|
56
|
+
// 3. Handle calendar reset boundary checking
|
|
57
|
+
if (now >= quota.billingCycleResetDate.getTime()) {
|
|
58
|
+
quota.totalRequestsThisMonth = 0;
|
|
59
|
+
quota.billingCycleResetDate = this.calculateNextResetDate();
|
|
60
|
+
// Record a zero-increment upsert to sync the new reset date to Postgres
|
|
61
|
+
this.enqueueIncrement(tenantId, 0);
|
|
62
|
+
}
|
|
63
|
+
// 4. Validate quota limit
|
|
64
|
+
if (quota.totalRequestsThisMonth >= quota.quotaLimit) {
|
|
65
|
+
return {
|
|
66
|
+
allowed: false,
|
|
67
|
+
reason: 'Monthly Cloud Quota Exceeded. Please upgrade your plan.',
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
// 5. Allow request, increment counter locally, and queue the increment
|
|
71
|
+
quota.totalRequestsThisMonth++;
|
|
72
|
+
this.enqueueIncrement(tenantId, 1);
|
|
73
|
+
return { allowed: true };
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Lazily loads tenant quota details from PostgreSQL in the background.
|
|
77
|
+
* Merges loaded DB counts with any in-memory increments that accumulated in the meantime.
|
|
78
|
+
*/
|
|
79
|
+
async lazyLoadTenantQuota(tenantId, defaultLimit) {
|
|
80
|
+
try {
|
|
81
|
+
const result = await this.pool.query('SELECT total_requests_this_month, quota_limit, billing_cycle_reset_date FROM tenant_quotas WHERE tenant_id = $1', [tenantId]);
|
|
82
|
+
const nextReset = this.calculateNextResetDate();
|
|
83
|
+
if (result.rows.length > 0) {
|
|
84
|
+
const row = result.rows[0];
|
|
85
|
+
const dbQuota = {
|
|
86
|
+
tenantId,
|
|
87
|
+
totalRequestsThisMonth: parseInt(row.total_requests_this_month, 10),
|
|
88
|
+
quotaLimit: parseInt(row.quota_limit, 10),
|
|
89
|
+
billingCycleResetDate: new Date(row.billing_cycle_reset_date),
|
|
90
|
+
};
|
|
91
|
+
const currentQuota = this.inMemoryQuotas.get(tenantId);
|
|
92
|
+
if (currentQuota) {
|
|
93
|
+
// Check if DB stored reset date has expired in the real world
|
|
94
|
+
if (Date.now() >= dbQuota.billingCycleResetDate.getTime()) {
|
|
95
|
+
dbQuota.totalRequestsThisMonth = 0;
|
|
96
|
+
dbQuota.billingCycleResetDate = nextReset;
|
|
97
|
+
}
|
|
98
|
+
// Merge: Add currently accumulated pending increments onto DB count
|
|
99
|
+
const pending = this.pendingIncrements.get(tenantId) || 0;
|
|
100
|
+
currentQuota.totalRequestsThisMonth = dbQuota.totalRequestsThisMonth + pending;
|
|
101
|
+
currentQuota.quotaLimit = dbQuota.quotaLimit;
|
|
102
|
+
currentQuota.billingCycleResetDate = dbQuota.billingCycleResetDate;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
// First-time tenant insertion in Postgres (UPSERT to initialize)
|
|
107
|
+
await this.pool.query(`INSERT INTO tenant_quotas (tenant_id, total_requests_this_month, quota_limit, billing_cycle_reset_date)
|
|
108
|
+
VALUES ($1, 0, $2, $3)
|
|
109
|
+
ON CONFLICT (tenant_id) DO NOTHING`, [tenantId, defaultLimit, nextReset]);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
catch (err) {
|
|
113
|
+
console.error(`[PostgresQuotaLimiter] Database load error for tenant ${tenantId}:`, err);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Queue increments in memory to await bulk batch flush
|
|
118
|
+
*/
|
|
119
|
+
enqueueIncrement(tenantId, amount) {
|
|
120
|
+
const current = this.pendingIncrements.get(tenantId) || 0;
|
|
121
|
+
this.pendingIncrements.set(tenantId, current + amount);
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Performs an atomic database bulk UPSERT of all pending in-memory increments.
|
|
125
|
+
* If a database error occurs, pending increments are safely rolled back and merged
|
|
126
|
+
* into the in-memory map to try again in the next batch flush cycle.
|
|
127
|
+
*/
|
|
128
|
+
async flushPendingIncrements() {
|
|
129
|
+
if (this.isFlushing || this.pendingIncrements.size === 0)
|
|
130
|
+
return;
|
|
131
|
+
this.isFlushing = true;
|
|
132
|
+
// Shallow copy and clear the queue immediately to avoid race conditions
|
|
133
|
+
// with new inbound requests accumulating during database I/O
|
|
134
|
+
const incrementsToFlush = new Map(this.pendingIncrements);
|
|
135
|
+
this.pendingIncrements.clear();
|
|
136
|
+
try {
|
|
137
|
+
const client = await this.pool.connect();
|
|
138
|
+
try {
|
|
139
|
+
await client.query('BEGIN');
|
|
140
|
+
for (const [tenantId, increment] of incrementsToFlush.entries()) {
|
|
141
|
+
const quota = this.inMemoryQuotas.get(tenantId);
|
|
142
|
+
const limit = quota ? quota.quotaLimit : 50000;
|
|
143
|
+
const resetDate = quota ? quota.billingCycleResetDate : this.calculateNextResetDate();
|
|
144
|
+
await client.query(`INSERT INTO tenant_quotas (tenant_id, total_requests_this_month, quota_limit, billing_cycle_reset_date)
|
|
145
|
+
VALUES ($1, $2, $3, $4)
|
|
146
|
+
ON CONFLICT (tenant_id) DO UPDATE
|
|
147
|
+
SET total_requests_this_month = tenant_quotas.total_requests_this_month + EXCLUDED.total_requests_this_month,
|
|
148
|
+
billing_cycle_reset_date = EXCLUDED.billing_cycle_reset_date,
|
|
149
|
+
updated_at = CURRENT_TIMESTAMP`, [tenantId, increment, limit, resetDate]);
|
|
150
|
+
}
|
|
151
|
+
await client.query('COMMIT');
|
|
152
|
+
}
|
|
153
|
+
catch (err) {
|
|
154
|
+
await client.query('ROLLBACK');
|
|
155
|
+
throw err;
|
|
156
|
+
}
|
|
157
|
+
finally {
|
|
158
|
+
client.release();
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
catch (err) {
|
|
162
|
+
console.error('[PostgresQuotaLimiter] Batch flush database error. Restoring increments:', err);
|
|
163
|
+
// Restore increments back to pending list so they are retried on the next run
|
|
164
|
+
for (const [tenantId, increment] of incrementsToFlush.entries()) {
|
|
165
|
+
const current = this.pendingIncrements.get(tenantId) || 0;
|
|
166
|
+
this.pendingIncrements.set(tenantId, current + increment);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
finally {
|
|
170
|
+
this.isFlushing = false;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Helper to calculate the 1st of the next calendar month at midnight UTC
|
|
175
|
+
*/
|
|
176
|
+
calculateNextResetDate() {
|
|
177
|
+
const d = new Date();
|
|
178
|
+
d.setUTCMonth(d.getUTCMonth() + 1);
|
|
179
|
+
d.setUTCDate(1);
|
|
180
|
+
d.setUTCHours(0, 0, 0, 0);
|
|
181
|
+
return d;
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* For test harness only: directly seed an in-memory quota entry
|
|
185
|
+
*/
|
|
186
|
+
seedQuotaForTest(tenantId, quota) {
|
|
187
|
+
this.inMemoryQuotas.set(tenantId, quota);
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* For test harness only: check in-memory status
|
|
191
|
+
*/
|
|
192
|
+
getInMemoryQuota(tenantId) {
|
|
193
|
+
return this.inMemoryQuotas.get(tenantId);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
//# sourceMappingURL=PostgresQuotaLimiter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PostgresQuotaLimiter.js","sourceRoot":"","sources":["../../../src/cloud/PostgresQuotaLimiter.ts"],"names":[],"mappings":"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"}
|
|
@@ -23,7 +23,7 @@ export declare class TenantCache {
|
|
|
23
23
|
data?: any;
|
|
24
24
|
similarity?: number;
|
|
25
25
|
}>;
|
|
26
|
-
store(key: string, input: string, data: any, ttl: number): Promise<void>;
|
|
26
|
+
store(key: string, input: string, data: any, ttl: number, maxEntries?: number): Promise<void>;
|
|
27
27
|
getStats(): Promise<TenantStats>;
|
|
28
28
|
private embed;
|
|
29
29
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TenantCache.d.ts","sourceRoot":"","sources":["../../../src/cloud/TenantCache.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EAAE,WAAW,EAAuD,MAAM,SAAS,CAAC;AAU3F,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,cAAc,CAAS;IAE/B,OAAO,CAAC,YAAY,CAAa;IACjC,OAAO,CAAC,gBAAgB,CAAa;IACrC,OAAO,CAAC,cAAc,CAA+B;IACrD,OAAO,CAAC,SAAS,CAAa;IAE9B,OAAO,CAAC,KAAK,CAA0B;gBAE3B,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM;IAS3E,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAW3B,KAAK,CACT,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC;QAAE,GAAG,EAAE,OAAO,CAAC;QAAC,IAAI,CAAC,EAAE,GAAG,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"TenantCache.d.ts","sourceRoot":"","sources":["../../../src/cloud/TenantCache.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EAAE,WAAW,EAAuD,MAAM,SAAS,CAAC;AAU3F,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,cAAc,CAAS;IAE/B,OAAO,CAAC,YAAY,CAAa;IACjC,OAAO,CAAC,gBAAgB,CAAa;IACrC,OAAO,CAAC,cAAc,CAA+B;IACrD,OAAO,CAAC,SAAS,CAAa;IAE9B,OAAO,CAAC,KAAK,CAA0B;gBAE3B,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM;IAS3E,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAW3B,KAAK,CACT,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC;QAAE,GAAG,EAAE,OAAO,CAAC;QAAC,IAAI,CAAC,EAAE,GAAG,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAuDvD,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,UAAU,SAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IA+B5F,QAAQ,IAAI,OAAO,CAAC,WAAW,CAAC;YAyBxB,KAAK;CAyCpB"}
|
|
@@ -49,9 +49,21 @@ export class TenantCache {
|
|
|
49
49
|
return { hit: false };
|
|
50
50
|
}
|
|
51
51
|
const meta = results.metadatas[0][0];
|
|
52
|
-
|
|
53
|
-
|
|
52
|
+
const age = Date.now() - meta.createdAt;
|
|
53
|
+
// 1. Enforce strict 7-day TTL cache eviction policy (Pro/SaaS tier)
|
|
54
|
+
const MAX_TTL_MS = 7 * 24 * 60 * 60 * 1000; // 7 days
|
|
55
|
+
if (age > MAX_TTL_MS) {
|
|
54
56
|
this.stats.misses++;
|
|
57
|
+
// Evict expired entry from vector database in background
|
|
58
|
+
const expiredId = results.ids[0][0];
|
|
59
|
+
this.chromaCollection.delete({ ids: [expiredId] }).catch(() => { });
|
|
60
|
+
return { hit: false };
|
|
61
|
+
}
|
|
62
|
+
// 2. Check custom entry TTL
|
|
63
|
+
if (meta.ttl > 0 && age > meta.ttl) {
|
|
64
|
+
this.stats.misses++;
|
|
65
|
+
const expiredId = results.ids[0][0];
|
|
66
|
+
this.chromaCollection.delete({ ids: [expiredId] }).catch(() => { });
|
|
55
67
|
return { hit: false };
|
|
56
68
|
}
|
|
57
69
|
this.stats.hits++;
|
|
@@ -63,10 +75,16 @@ export class TenantCache {
|
|
|
63
75
|
}
|
|
64
76
|
}
|
|
65
77
|
// ─── Store ──────────────────────────────────────────────────────────────────
|
|
66
|
-
async store(key, input, data, ttl) {
|
|
78
|
+
async store(key, input, data, ttl, maxEntries = 10000) {
|
|
67
79
|
if (!this.chromaCollection)
|
|
68
80
|
await this.initialize();
|
|
69
81
|
try {
|
|
82
|
+
// Enforce maximum cache entries constraint to prevent storage bloat
|
|
83
|
+
const count = await this.chromaCollection.count();
|
|
84
|
+
if (count >= maxEntries) {
|
|
85
|
+
console.warn(`⚠️ [TenantCache:${this.tenantId}] Vector cache limit reached (${count}/${maxEntries} entries). Skipping store to prevent storage bloat.`);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
70
88
|
const embedding = await this.embed(input);
|
|
71
89
|
await this.chromaCollection.upsert({
|
|
72
90
|
ids: [key],
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TenantCache.js","sourceRoot":"","sources":["../../../src/cloud/TenantCache.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EAA4B,sBAAsB,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAU3F,MAAM,OAAO,WAAW;IActB,YAAY,QAAgB,EAAE,SAAiB,EAAE,SAAiB,EAAE,KAAa;QAPzE,iBAAY,GAAQ,IAAI,CAAC;QACzB,qBAAgB,GAAQ,IAAI,CAAC;QAC7B,mBAAc,GAAG,IAAI,GAAG,EAAoB,CAAC;QAC7C,cAAS,GAAQ,IAAI,CAAC;QAEtB,UAAK,GAAG,EAAE,IAAI,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC;QAGrC,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzB,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;QAC3B,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;QAC3B,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QACnB,iDAAiD;QACjD,IAAI,CAAC,cAAc,GAAG,SAAS,QAAQ,CAAC,OAAO,CAAC,aAAa,EAAE,GAAG,CAAC,CAAC,WAAW,EAAE,EAAE,CAAC;IACtF,CAAC;IAED,KAAK,CAAC,UAAU;QACd,MAAM,EAAE,YAAY,EAAE,GAAG,MAAM,MAAM,CAAC,UAAU,CAAC,CAAC;QAClD,IAAI,CAAC,YAAY,GAAG,IAAI,YAAY,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,EAAE,CAAC,CAAC;QAC/D,IAAI,CAAC,gBAAgB,GAAG,MAAM,IAAI,CAAC,YAAY,CAAC,qBAAqB,CAAC;YACpE,IAAI,EAAE,IAAI,CAAC,cAAc;YACzB,QAAQ,EAAE,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,CAAC,QAAQ,EAAE;SAC5D,CAAC,CAAC;IACL,CAAC;IAED,+EAA+E;IAE/E,KAAK,CAAC,KAAK,CACT,KAAa,EACb,SAAiB;QAEjB,IAAI,CAAC,IAAI,CAAC,gBAAgB;YAAE,MAAM,IAAI,CAAC,UAAU,EAAE,CAAC;QAEpD,IAAI,CAAC;YACH,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;YAC1C,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,gBAAgB,CAAC,KAAK,CAAC;gBAChD,eAAe,EAAE,CAAC,SAAS,CAAC;gBAC5B,QAAQ,EAAE,CAAC;gBACX,OAAO,EAAE,CAAC,WAAW,EAAE,WAAW,CAAC;aACpC,CAAC,CAAC;YAEH,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC;gBAC9B,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC;gBACpB,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC;YACxB,CAAC;YAED,MAAM,QAAQ,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YACzC,MAAM,UAAU,GAAG,CAAC,GAAG,QAAQ,CAAC;YAEhC,IAAI,UAAU,GAAG,SAAS,EAAE,CAAC;gBAC3B,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC;gBACpB,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC;YACxB,CAAC;YAED,MAAM,IAAI,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"TenantCache.js","sourceRoot":"","sources":["../../../src/cloud/TenantCache.ts"],"names":[],"mappings":"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"}
|
|
@@ -10,8 +10,10 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import { KeyManager } from './KeyManager';
|
|
12
12
|
import { TenantCache } from './TenantCache';
|
|
13
|
+
import { PostgresQuotaLimiter } from './PostgresQuotaLimiter';
|
|
13
14
|
declare const app: import("express-serve-static-core").Express;
|
|
14
15
|
declare const keyManager: KeyManager;
|
|
15
16
|
declare const tenantCaches: Map<string, TenantCache>;
|
|
16
|
-
|
|
17
|
+
declare let postgresQuotaLimiter: PostgresQuotaLimiter | null;
|
|
18
|
+
export { app, keyManager, tenantCaches, postgresQuotaLimiter };
|
|
17
19
|
//# sourceMappingURL=server.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../../src/cloud/server.ts"],"names":[],"mappings":";AACA;;;;;;;;GAQG;AAIH,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../../src/cloud/server.ts"],"names":[],"mappings":";AACA;;;;;;;;GAQG;AAIH,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAI5C,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAG9D,QAAA,MAAM,GAAG,6CAAY,CAAC;AAYtB,QAAA,MAAM,UAAU,YAAmB,CAAC;AACpC,QAAA,MAAM,YAAY,0BAAiC,CAAC;AAIpD,QAAA,IAAI,oBAAoB,EAAE,oBAAoB,GAAG,IAAW,CAAC;AA6U7D,OAAO,EAAE,GAAG,EAAE,UAAU,EAAE,YAAY,EAAE,oBAAoB,EAAE,CAAC"}
|
package/dist/esm/cloud/server.js
CHANGED
|
@@ -12,6 +12,10 @@ import express from 'express';
|
|
|
12
12
|
import crypto from 'crypto';
|
|
13
13
|
import { KeyManager } from './KeyManager';
|
|
14
14
|
import { TenantCache } from './TenantCache';
|
|
15
|
+
import { PLAN_LIMITS } from './types';
|
|
16
|
+
import { BurstRateLimiter } from './BurstRateLimiter';
|
|
17
|
+
import { Pool } from 'pg';
|
|
18
|
+
import { PostgresQuotaLimiter } from './PostgresQuotaLimiter';
|
|
15
19
|
const app = express();
|
|
16
20
|
app.use(express.json({ limit: '10mb' }));
|
|
17
21
|
// ─── Config ───────────────────────────────────────────────────────────────────
|
|
@@ -22,19 +26,50 @@ const OLLAMA_MODEL = process.env.OLLAMA_MODEL || 'nomic-embed-text';
|
|
|
22
26
|
// ─── State ────────────────────────────────────────────────────────────────────
|
|
23
27
|
const keyManager = new KeyManager();
|
|
24
28
|
const tenantCaches = new Map();
|
|
29
|
+
const burstRateLimiter = new BurstRateLimiter(10, 1000); // 10 RPS cap
|
|
30
|
+
// Setup PostgreSQL-backed Lean Rate Limiter if DATABASE_URL is configured
|
|
31
|
+
let postgresQuotaLimiter = null;
|
|
32
|
+
if (process.env.DATABASE_URL) {
|
|
33
|
+
const pgPool = new Pool({
|
|
34
|
+
connectionString: process.env.DATABASE_URL,
|
|
35
|
+
ssl: process.env.NODE_ENV === 'production' ? { rejectUnauthorized: false } : undefined,
|
|
36
|
+
});
|
|
37
|
+
postgresQuotaLimiter = new PostgresQuotaLimiter(pgPool, 5000);
|
|
38
|
+
postgresQuotaLimiter.start();
|
|
39
|
+
}
|
|
25
40
|
function authenticate(req, res, next) {
|
|
26
41
|
const auth = req.headers.authorization;
|
|
27
42
|
if (!auth || !auth.startsWith('Bearer ')) {
|
|
28
43
|
return res.status(401).json({ error: 'Missing or invalid Authorization header' });
|
|
29
44
|
}
|
|
30
45
|
const keyId = auth.replace('Bearer ', '');
|
|
46
|
+
// 1. Enforce Sliding Window Burst Rate Limiting (10 RPS)
|
|
47
|
+
const burstCheck = burstRateLimiter.check(keyId);
|
|
48
|
+
if (!burstCheck.allowed) {
|
|
49
|
+
return res.status(429).json({
|
|
50
|
+
error: 'Too Many Requests. Burst rate limit of 10 RPS exceeded. Please back off.',
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
// 2. Validate Key Status & Daily Limits
|
|
31
54
|
const validation = keyManager.validate(keyId);
|
|
32
55
|
if (!validation.valid) {
|
|
33
|
-
|
|
56
|
+
const isLimitExceeded = validation.reason?.includes('limit') || validation.reason?.includes('Quota');
|
|
57
|
+
return res.status(isLimitExceeded ? 429 : 403).json({
|
|
34
58
|
error: validation.reason,
|
|
35
59
|
});
|
|
36
60
|
}
|
|
37
|
-
|
|
61
|
+
const apiKey = validation.key;
|
|
62
|
+
// 3. Enforce PostgreSQL Lean Monthly Quota
|
|
63
|
+
if (postgresQuotaLimiter) {
|
|
64
|
+
const monthlyLimit = PLAN_LIMITS[apiKey.plan]?.monthlyRequests || 50000;
|
|
65
|
+
const quotaCheck = postgresQuotaLimiter.checkQuota(apiKey.tenantId, monthlyLimit);
|
|
66
|
+
if (!quotaCheck.allowed) {
|
|
67
|
+
return res.status(429).json({
|
|
68
|
+
error: quotaCheck.reason,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
req.apiKey = apiKey;
|
|
38
73
|
keyManager.recordRequest(keyId);
|
|
39
74
|
next();
|
|
40
75
|
}
|
|
@@ -89,7 +124,8 @@ app.post('/v1/cache', authenticate, async (req, res) => {
|
|
|
89
124
|
}
|
|
90
125
|
try {
|
|
91
126
|
const cache = getTenantCache(req.apiKey.tenantId);
|
|
92
|
-
|
|
127
|
+
const maxEntries = PLAN_LIMITS[req.apiKey.plan]?.cacheEntries || 10000;
|
|
128
|
+
await cache.store(key, input, data, ttl, maxEntries);
|
|
93
129
|
res.json({ ok: true });
|
|
94
130
|
}
|
|
95
131
|
catch (err) {
|
|
@@ -207,7 +243,7 @@ app.post(['/v1/payments/stripe-webhook', '/api/v1/payments/stripe-webhook'], (re
|
|
|
207
243
|
plan: 'pro',
|
|
208
244
|
createdAt: Date.now(),
|
|
209
245
|
active: true,
|
|
210
|
-
usage: { today: 0, total: 0, lastReset: Date.now() },
|
|
246
|
+
usage: { today: 0, monthly: 0, total: 0, lastReset: Date.now(), lastMonthlyReset: Date.now() },
|
|
211
247
|
};
|
|
212
248
|
// Register in KeyManager keys Map
|
|
213
249
|
// @ts-ignore CJS private access bypass
|
|
@@ -272,5 +308,5 @@ app.listen(PORT, () => {
|
|
|
272
308
|
console.log(' POST /admin/keys/:id/upgrade — Upgrade plan');
|
|
273
309
|
console.log('');
|
|
274
310
|
});
|
|
275
|
-
export { app, keyManager, tenantCaches };
|
|
311
|
+
export { app, keyManager, tenantCaches, postgresQuotaLimiter };
|
|
276
312
|
//# sourceMappingURL=server.js.map
|