te.js 2.1.0 → 2.1.2
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/README.md +197 -196
- package/auto-docs/analysis/handler-analyzer.js +58 -58
- package/auto-docs/analysis/source-resolver.js +101 -101
- package/auto-docs/constants.js +37 -37
- package/auto-docs/docs-llm/index.js +7 -7
- package/auto-docs/docs-llm/prompts.js +222 -222
- package/auto-docs/docs-llm/provider.js +132 -132
- package/auto-docs/index.js +146 -146
- package/auto-docs/openapi/endpoint-processor.js +277 -277
- package/auto-docs/openapi/generator.js +107 -107
- package/auto-docs/openapi/level3.js +131 -131
- package/auto-docs/openapi/spec-builders.js +244 -244
- package/auto-docs/ui/docs-ui.js +186 -186
- package/auto-docs/utils/logger.js +17 -17
- package/auto-docs/utils/strip-usage.js +10 -10
- package/cli/docs-command.js +315 -315
- package/cli/fly-command.js +71 -71
- package/cli/index.js +56 -56
- package/cors/index.js +71 -0
- package/database/index.js +165 -165
- package/database/mongodb.js +146 -146
- package/database/redis.js +201 -201
- package/docs/README.md +36 -36
- package/docs/ammo.md +362 -362
- package/docs/api-reference.md +490 -490
- package/docs/auto-docs.md +216 -216
- package/docs/cli.md +152 -152
- package/docs/configuration.md +275 -275
- package/docs/database.md +390 -390
- package/docs/error-handling.md +438 -438
- package/docs/file-uploads.md +333 -333
- package/docs/getting-started.md +214 -214
- package/docs/middleware.md +355 -355
- package/docs/rate-limiting.md +393 -393
- package/docs/routing.md +302 -302
- package/lib/llm/client.js +73 -0
- package/lib/llm/index.js +7 -0
- package/lib/llm/parse.js +89 -0
- package/package.json +64 -62
- package/rate-limit/algorithms/fixed-window.js +141 -141
- package/rate-limit/algorithms/sliding-window.js +147 -147
- package/rate-limit/algorithms/token-bucket.js +115 -115
- package/rate-limit/base.js +165 -165
- package/rate-limit/index.js +147 -147
- package/rate-limit/storage/base.js +104 -104
- package/rate-limit/storage/memory.js +101 -101
- package/rate-limit/storage/redis.js +88 -88
- package/server/ammo/body-parser.js +220 -220
- package/server/ammo/dispatch-helper.js +103 -103
- package/server/ammo/enhancer.js +57 -57
- package/server/ammo.js +454 -415
- package/server/endpoint.js +97 -74
- package/server/error.js +9 -9
- package/server/errors/code-context.js +125 -125
- package/server/errors/llm-error-service.js +140 -140
- package/server/files/helper.js +33 -33
- package/server/files/uploader.js +143 -143
- package/server/handler.js +158 -119
- package/server/target.js +185 -175
- package/server/targets/middleware-validator.js +22 -22
- package/server/targets/path-validator.js +21 -21
- package/server/targets/registry.js +160 -160
- package/server/targets/shoot-validator.js +21 -21
- package/te.js +428 -402
- package/utils/auto-register.js +17 -17
- package/utils/configuration.js +64 -64
- package/utils/errors-llm-config.js +84 -84
- package/utils/request-logger.js +43 -43
- package/utils/status-codes.js +82 -82
- package/utils/tejas-entrypoint-html.js +18 -18
package/lib/llm/parse.js
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parse JSON from LLM response text (handles markdown code blocks).
|
|
3
|
+
* Shared by auto-docs, error-inference, and other LLM features.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Extract the first JSON object from a string.
|
|
8
|
+
* @param {string} str - Raw LLM response
|
|
9
|
+
* @returns {object|null}
|
|
10
|
+
*/
|
|
11
|
+
export function extractJSON(str) {
|
|
12
|
+
if (!str || typeof str !== 'string') return null;
|
|
13
|
+
const trimmed = str.trim();
|
|
14
|
+
const open = trimmed.indexOf('{');
|
|
15
|
+
if (open === -1) return null;
|
|
16
|
+
let depth = 0;
|
|
17
|
+
let end = -1;
|
|
18
|
+
for (let i = open; i < trimmed.length; i++) {
|
|
19
|
+
if (trimmed[i] === '{') depth++;
|
|
20
|
+
else if (trimmed[i] === '}') {
|
|
21
|
+
depth--;
|
|
22
|
+
if (depth === 0) {
|
|
23
|
+
end = i + 1;
|
|
24
|
+
break;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
if (end === -1) return null;
|
|
29
|
+
try {
|
|
30
|
+
return JSON.parse(trimmed.slice(open, end));
|
|
31
|
+
} catch {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Extract the first JSON array from a string.
|
|
38
|
+
* @param {string} str - Raw LLM response
|
|
39
|
+
* @returns {Array|null}
|
|
40
|
+
*/
|
|
41
|
+
export function extractJSONArray(str) {
|
|
42
|
+
if (!str || typeof str !== 'string') return null;
|
|
43
|
+
const trimmed = str.trim();
|
|
44
|
+
const open = trimmed.indexOf('[');
|
|
45
|
+
if (open === -1) return null;
|
|
46
|
+
let depth = 0;
|
|
47
|
+
let end = -1;
|
|
48
|
+
for (let i = open; i < trimmed.length; i++) {
|
|
49
|
+
if (trimmed[i] === '[') depth++;
|
|
50
|
+
else if (trimmed[i] === ']') {
|
|
51
|
+
depth--;
|
|
52
|
+
if (depth === 0) {
|
|
53
|
+
end = i + 1;
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (end === -1) return null;
|
|
59
|
+
try {
|
|
60
|
+
return JSON.parse(trimmed.slice(open, end));
|
|
61
|
+
} catch {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Reconcile LLM-ordered tag names with actual tag objects. Returns tags in desired order;
|
|
68
|
+
* any tag not in orderedTagNames is appended at the end.
|
|
69
|
+
* @param {string[]} orderedTagNames - Tag names in desired order (from LLM)
|
|
70
|
+
* @param {Array<{ name: string, description?: string }>} tags - Current spec.tags
|
|
71
|
+
* @returns {Array<{ name: string, description?: string }>} Tags reordered
|
|
72
|
+
*/
|
|
73
|
+
export function reconcileOrderedTags(orderedTagNames, tags) {
|
|
74
|
+
if (!Array.isArray(tags) || !tags.length) return [];
|
|
75
|
+
if (!Array.isArray(orderedTagNames) || !orderedTagNames.length) return [...tags];
|
|
76
|
+
const byName = new Map(tags.map((t) => [t.name, t]));
|
|
77
|
+
const ordered = [];
|
|
78
|
+
for (const name of orderedTagNames) {
|
|
79
|
+
const tag = byName.get(name);
|
|
80
|
+
if (tag) {
|
|
81
|
+
ordered.push(tag);
|
|
82
|
+
byName.delete(name);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
for (const [, tag] of byName) {
|
|
86
|
+
ordered.push(tag);
|
|
87
|
+
}
|
|
88
|
+
return ordered;
|
|
89
|
+
}
|
package/package.json
CHANGED
|
@@ -1,62 +1,64 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "te.js",
|
|
3
|
-
"version": "2.1.
|
|
4
|
-
"description": "
|
|
5
|
-
"type": "module",
|
|
6
|
-
"main": "te.js",
|
|
7
|
-
"bin": {
|
|
8
|
-
"tejas": "cli/index.js"
|
|
9
|
-
},
|
|
10
|
-
"scripts": {
|
|
11
|
-
"start": "node te.js",
|
|
12
|
-
"test": "vitest",
|
|
13
|
-
"test:run": "vitest run",
|
|
14
|
-
"test:coverage": "vitest run --coverage",
|
|
15
|
-
"prepare": "husky"
|
|
16
|
-
},
|
|
17
|
-
"author": "Hirak",
|
|
18
|
-
"license": "ISC",
|
|
19
|
-
"devDependencies": {
|
|
20
|
-
"@types/node": "^20.12.5",
|
|
21
|
-
"husky": "^9.0.11",
|
|
22
|
-
"lint-staged": "^15.2.2",
|
|
23
|
-
"prettier": "3.2.5",
|
|
24
|
-
"vitest": "^4.0.18"
|
|
25
|
-
},
|
|
26
|
-
"repository": {
|
|
27
|
-
"type": "git",
|
|
28
|
-
"url": "git+ssh://git@github.com/hirakchhatbar/te.js.git"
|
|
29
|
-
},
|
|
30
|
-
"files": [
|
|
31
|
-
"te.js",
|
|
32
|
-
"cli",
|
|
33
|
-
"
|
|
34
|
-
"
|
|
35
|
-
"
|
|
36
|
-
"
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
"docs"
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
"
|
|
45
|
-
"
|
|
46
|
-
"
|
|
47
|
-
"
|
|
48
|
-
"
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "te.js",
|
|
3
|
+
"version": "2.1.2",
|
|
4
|
+
"description": "AI Native Node.js Framework",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "te.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"tejas": "cli/index.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"start": "node te.js",
|
|
12
|
+
"test": "vitest",
|
|
13
|
+
"test:run": "vitest run",
|
|
14
|
+
"test:coverage": "vitest run --coverage",
|
|
15
|
+
"prepare": "husky"
|
|
16
|
+
},
|
|
17
|
+
"author": "Hirak",
|
|
18
|
+
"license": "ISC",
|
|
19
|
+
"devDependencies": {
|
|
20
|
+
"@types/node": "^20.12.5",
|
|
21
|
+
"husky": "^9.0.11",
|
|
22
|
+
"lint-staged": "^15.2.2",
|
|
23
|
+
"prettier": "3.2.5",
|
|
24
|
+
"vitest": "^4.0.18"
|
|
25
|
+
},
|
|
26
|
+
"repository": {
|
|
27
|
+
"type": "git",
|
|
28
|
+
"url": "git+ssh://git@github.com/hirakchhatbar/te.js.git"
|
|
29
|
+
},
|
|
30
|
+
"files": [
|
|
31
|
+
"te.js",
|
|
32
|
+
"cli",
|
|
33
|
+
"cors",
|
|
34
|
+
"server",
|
|
35
|
+
"database",
|
|
36
|
+
"rate-limit",
|
|
37
|
+
"utils",
|
|
38
|
+
"lib",
|
|
39
|
+
"auto-docs",
|
|
40
|
+
"README.md",
|
|
41
|
+
"docs"
|
|
42
|
+
],
|
|
43
|
+
"dependencies": {
|
|
44
|
+
"ansi-colors": "^4.1.3",
|
|
45
|
+
"filesize": "^10.1.1",
|
|
46
|
+
"formidable": "^3.5.1",
|
|
47
|
+
"mime": "^4.0.1",
|
|
48
|
+
"statuses": "^2.0.1",
|
|
49
|
+
"tej-env": "^1.1.3",
|
|
50
|
+
"tej-logger": "^1.2.1"
|
|
51
|
+
},
|
|
52
|
+
"husky": {
|
|
53
|
+
"hooks": {
|
|
54
|
+
"pre-commit": "lint-staged",
|
|
55
|
+
"pre-push": "npm test"
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
"lint-staged": {
|
|
59
|
+
"src/**/*.{js,jsx}": [
|
|
60
|
+
"pretty-quick --staged",
|
|
61
|
+
"eslint --fix"
|
|
62
|
+
]
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -1,141 +1,141 @@
|
|
|
1
|
-
import RateLimiter from '../base.js';
|
|
2
|
-
import TejError from '../../server/error.js';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Fixed Window Rate Limiter Implementation
|
|
6
|
-
*
|
|
7
|
-
* @extends RateLimiter
|
|
8
|
-
* @description
|
|
9
|
-
* The fixed window algorithm uses discrete time windows to track and limit requests.
|
|
10
|
-
* It's the simplest rate limiting approach, but can allow request spikes at window boundaries
|
|
11
|
-
* when using rolling windows. This can be mitigated by using strict window alignment with
|
|
12
|
-
* clock time.
|
|
13
|
-
*
|
|
14
|
-
* Key features:
|
|
15
|
-
* - Simple to understand and implement
|
|
16
|
-
* - Low memory usage (only stores counter and window start time)
|
|
17
|
-
* - Optional strict window alignment with clock time
|
|
18
|
-
* - Best for cases where precise spacing of requests is not critical
|
|
19
|
-
*
|
|
20
|
-
* Window types:
|
|
21
|
-
* 1. Rolling windows: Start when first request arrives
|
|
22
|
-
* 2. Strict windows: Align with clock time (e.g. every minute)
|
|
23
|
-
*
|
|
24
|
-
* @example
|
|
25
|
-
* // Create a fixed window rate limiter with strict clock alignment
|
|
26
|
-
* const limiter = new FixedWindowRateLimiter({
|
|
27
|
-
* maxRequests: 60, // Allow 60 requests per minute
|
|
28
|
-
* timeWindowSeconds: 60,
|
|
29
|
-
* fixedWindow: {
|
|
30
|
-
* strictWindow: true // Align windows with clock minutes
|
|
31
|
-
* }
|
|
32
|
-
* });
|
|
33
|
-
*
|
|
34
|
-
* // Use in an API endpoint
|
|
35
|
-
* async function handleRequest(ip) {
|
|
36
|
-
* const result = await limiter.consume(ip);
|
|
37
|
-
* if (!result.success) {
|
|
38
|
-
* throw new TejError(429, 'Rate limit exceeded');
|
|
39
|
-
* }
|
|
40
|
-
* // Process request...
|
|
41
|
-
* }
|
|
42
|
-
*/
|
|
43
|
-
class FixedWindowRateLimiter extends RateLimiter {
|
|
44
|
-
constructor(options) {
|
|
45
|
-
if (!options.fixedWindowConfig) {
|
|
46
|
-
options.fixedWindowConfig = {}; // Ensure defaults are set in base class
|
|
47
|
-
}
|
|
48
|
-
super(options);
|
|
49
|
-
|
|
50
|
-
if (!this.fixedWindowOptions) {
|
|
51
|
-
throw new TejError(
|
|
52
|
-
400,
|
|
53
|
-
'FixedWindowRateLimiter requires fixedWindowConfig options',
|
|
54
|
-
);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Check if a request should be allowed within the current window
|
|
60
|
-
*
|
|
61
|
-
* @param {string} identifier - Unique identifier for rate limiting (e.g., IP address, user ID)
|
|
62
|
-
* @returns {Promise<Object>} Rate limit check result
|
|
63
|
-
* @returns {boolean} result.success - Whether the request is allowed
|
|
64
|
-
* @returns {number} result.remainingRequests - Number of requests remaining in the window
|
|
65
|
-
* @returns {number} result.resetTime - Unix timestamp when the current window ends
|
|
66
|
-
*/
|
|
67
|
-
async consume(identifier) {
|
|
68
|
-
const key = this.getKey(identifier);
|
|
69
|
-
const now = Date.now();
|
|
70
|
-
const options = this.getAlgorithmOptions('fixed-window');
|
|
71
|
-
|
|
72
|
-
// If using strict windows, align window start with clock
|
|
73
|
-
const windowStart = options.strictWindow
|
|
74
|
-
? Math.floor(now / (this.options.timeWindowSeconds * 1000)) *
|
|
75
|
-
(this.options.timeWindowSeconds * 1000)
|
|
76
|
-
: now;
|
|
77
|
-
|
|
78
|
-
const stored = await this.storage.get(key);
|
|
79
|
-
if (!stored) {
|
|
80
|
-
await this.storage.set(
|
|
81
|
-
key,
|
|
82
|
-
{
|
|
83
|
-
counter: 1,
|
|
84
|
-
startTime: windowStart,
|
|
85
|
-
},
|
|
86
|
-
this.options.timeWindowSeconds,
|
|
87
|
-
);
|
|
88
|
-
|
|
89
|
-
return {
|
|
90
|
-
success: true,
|
|
91
|
-
remainingRequests: this.options.maxRequests - 1,
|
|
92
|
-
resetTime: Math.floor(
|
|
93
|
-
windowStart / 1000 + this.options.timeWindowSeconds,
|
|
94
|
-
),
|
|
95
|
-
};
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// If using strict windows, check if we need to start a new window
|
|
99
|
-
if (options.strictWindow && stored.startTime < windowStart) {
|
|
100
|
-
await this.storage.set(
|
|
101
|
-
key,
|
|
102
|
-
{
|
|
103
|
-
counter: 1,
|
|
104
|
-
startTime: windowStart,
|
|
105
|
-
},
|
|
106
|
-
this.options.timeWindowSeconds,
|
|
107
|
-
);
|
|
108
|
-
|
|
109
|
-
return {
|
|
110
|
-
success: true,
|
|
111
|
-
remainingRequests: this.options.maxRequests - 1,
|
|
112
|
-
resetTime: Math.floor(
|
|
113
|
-
windowStart / 1000 + this.options.timeWindowSeconds,
|
|
114
|
-
),
|
|
115
|
-
};
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
if (stored.counter >= this.options.maxRequests) {
|
|
119
|
-
return {
|
|
120
|
-
success: false,
|
|
121
|
-
remainingRequests: 0,
|
|
122
|
-
resetTime: Math.floor(
|
|
123
|
-
stored.startTime / 1000 + this.options.timeWindowSeconds,
|
|
124
|
-
),
|
|
125
|
-
};
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
stored.counter++;
|
|
129
|
-
await this.storage.set(key, stored, this.options.timeWindowSeconds);
|
|
130
|
-
|
|
131
|
-
return {
|
|
132
|
-
success: true,
|
|
133
|
-
remainingRequests: this.options.maxRequests - stored.counter,
|
|
134
|
-
resetTime: Math.floor(
|
|
135
|
-
stored.startTime / 1000 + this.options.timeWindowSeconds,
|
|
136
|
-
),
|
|
137
|
-
};
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
export default FixedWindowRateLimiter;
|
|
1
|
+
import RateLimiter from '../base.js';
|
|
2
|
+
import TejError from '../../server/error.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Fixed Window Rate Limiter Implementation
|
|
6
|
+
*
|
|
7
|
+
* @extends RateLimiter
|
|
8
|
+
* @description
|
|
9
|
+
* The fixed window algorithm uses discrete time windows to track and limit requests.
|
|
10
|
+
* It's the simplest rate limiting approach, but can allow request spikes at window boundaries
|
|
11
|
+
* when using rolling windows. This can be mitigated by using strict window alignment with
|
|
12
|
+
* clock time.
|
|
13
|
+
*
|
|
14
|
+
* Key features:
|
|
15
|
+
* - Simple to understand and implement
|
|
16
|
+
* - Low memory usage (only stores counter and window start time)
|
|
17
|
+
* - Optional strict window alignment with clock time
|
|
18
|
+
* - Best for cases where precise spacing of requests is not critical
|
|
19
|
+
*
|
|
20
|
+
* Window types:
|
|
21
|
+
* 1. Rolling windows: Start when first request arrives
|
|
22
|
+
* 2. Strict windows: Align with clock time (e.g. every minute)
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* // Create a fixed window rate limiter with strict clock alignment
|
|
26
|
+
* const limiter = new FixedWindowRateLimiter({
|
|
27
|
+
* maxRequests: 60, // Allow 60 requests per minute
|
|
28
|
+
* timeWindowSeconds: 60,
|
|
29
|
+
* fixedWindow: {
|
|
30
|
+
* strictWindow: true // Align windows with clock minutes
|
|
31
|
+
* }
|
|
32
|
+
* });
|
|
33
|
+
*
|
|
34
|
+
* // Use in an API endpoint
|
|
35
|
+
* async function handleRequest(ip) {
|
|
36
|
+
* const result = await limiter.consume(ip);
|
|
37
|
+
* if (!result.success) {
|
|
38
|
+
* throw new TejError(429, 'Rate limit exceeded');
|
|
39
|
+
* }
|
|
40
|
+
* // Process request...
|
|
41
|
+
* }
|
|
42
|
+
*/
|
|
43
|
+
class FixedWindowRateLimiter extends RateLimiter {
|
|
44
|
+
constructor(options) {
|
|
45
|
+
if (!options.fixedWindowConfig) {
|
|
46
|
+
options.fixedWindowConfig = {}; // Ensure defaults are set in base class
|
|
47
|
+
}
|
|
48
|
+
super(options);
|
|
49
|
+
|
|
50
|
+
if (!this.fixedWindowOptions) {
|
|
51
|
+
throw new TejError(
|
|
52
|
+
400,
|
|
53
|
+
'FixedWindowRateLimiter requires fixedWindowConfig options',
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Check if a request should be allowed within the current window
|
|
60
|
+
*
|
|
61
|
+
* @param {string} identifier - Unique identifier for rate limiting (e.g., IP address, user ID)
|
|
62
|
+
* @returns {Promise<Object>} Rate limit check result
|
|
63
|
+
* @returns {boolean} result.success - Whether the request is allowed
|
|
64
|
+
* @returns {number} result.remainingRequests - Number of requests remaining in the window
|
|
65
|
+
* @returns {number} result.resetTime - Unix timestamp when the current window ends
|
|
66
|
+
*/
|
|
67
|
+
async consume(identifier) {
|
|
68
|
+
const key = this.getKey(identifier);
|
|
69
|
+
const now = Date.now();
|
|
70
|
+
const options = this.getAlgorithmOptions('fixed-window');
|
|
71
|
+
|
|
72
|
+
// If using strict windows, align window start with clock
|
|
73
|
+
const windowStart = options.strictWindow
|
|
74
|
+
? Math.floor(now / (this.options.timeWindowSeconds * 1000)) *
|
|
75
|
+
(this.options.timeWindowSeconds * 1000)
|
|
76
|
+
: now;
|
|
77
|
+
|
|
78
|
+
const stored = await this.storage.get(key);
|
|
79
|
+
if (!stored) {
|
|
80
|
+
await this.storage.set(
|
|
81
|
+
key,
|
|
82
|
+
{
|
|
83
|
+
counter: 1,
|
|
84
|
+
startTime: windowStart,
|
|
85
|
+
},
|
|
86
|
+
this.options.timeWindowSeconds,
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
success: true,
|
|
91
|
+
remainingRequests: this.options.maxRequests - 1,
|
|
92
|
+
resetTime: Math.floor(
|
|
93
|
+
windowStart / 1000 + this.options.timeWindowSeconds,
|
|
94
|
+
),
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// If using strict windows, check if we need to start a new window
|
|
99
|
+
if (options.strictWindow && stored.startTime < windowStart) {
|
|
100
|
+
await this.storage.set(
|
|
101
|
+
key,
|
|
102
|
+
{
|
|
103
|
+
counter: 1,
|
|
104
|
+
startTime: windowStart,
|
|
105
|
+
},
|
|
106
|
+
this.options.timeWindowSeconds,
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
return {
|
|
110
|
+
success: true,
|
|
111
|
+
remainingRequests: this.options.maxRequests - 1,
|
|
112
|
+
resetTime: Math.floor(
|
|
113
|
+
windowStart / 1000 + this.options.timeWindowSeconds,
|
|
114
|
+
),
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (stored.counter >= this.options.maxRequests) {
|
|
119
|
+
return {
|
|
120
|
+
success: false,
|
|
121
|
+
remainingRequests: 0,
|
|
122
|
+
resetTime: Math.floor(
|
|
123
|
+
stored.startTime / 1000 + this.options.timeWindowSeconds,
|
|
124
|
+
),
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
stored.counter++;
|
|
129
|
+
await this.storage.set(key, stored, this.options.timeWindowSeconds);
|
|
130
|
+
|
|
131
|
+
return {
|
|
132
|
+
success: true,
|
|
133
|
+
remainingRequests: this.options.maxRequests - stored.counter,
|
|
134
|
+
resetTime: Math.floor(
|
|
135
|
+
stored.startTime / 1000 + this.options.timeWindowSeconds,
|
|
136
|
+
),
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export default FixedWindowRateLimiter;
|