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.
Files changed (70) hide show
  1. package/README.md +197 -196
  2. package/auto-docs/analysis/handler-analyzer.js +58 -58
  3. package/auto-docs/analysis/source-resolver.js +101 -101
  4. package/auto-docs/constants.js +37 -37
  5. package/auto-docs/docs-llm/index.js +7 -7
  6. package/auto-docs/docs-llm/prompts.js +222 -222
  7. package/auto-docs/docs-llm/provider.js +132 -132
  8. package/auto-docs/index.js +146 -146
  9. package/auto-docs/openapi/endpoint-processor.js +277 -277
  10. package/auto-docs/openapi/generator.js +107 -107
  11. package/auto-docs/openapi/level3.js +131 -131
  12. package/auto-docs/openapi/spec-builders.js +244 -244
  13. package/auto-docs/ui/docs-ui.js +186 -186
  14. package/auto-docs/utils/logger.js +17 -17
  15. package/auto-docs/utils/strip-usage.js +10 -10
  16. package/cli/docs-command.js +315 -315
  17. package/cli/fly-command.js +71 -71
  18. package/cli/index.js +56 -56
  19. package/cors/index.js +71 -0
  20. package/database/index.js +165 -165
  21. package/database/mongodb.js +146 -146
  22. package/database/redis.js +201 -201
  23. package/docs/README.md +36 -36
  24. package/docs/ammo.md +362 -362
  25. package/docs/api-reference.md +490 -490
  26. package/docs/auto-docs.md +216 -216
  27. package/docs/cli.md +152 -152
  28. package/docs/configuration.md +275 -275
  29. package/docs/database.md +390 -390
  30. package/docs/error-handling.md +438 -438
  31. package/docs/file-uploads.md +333 -333
  32. package/docs/getting-started.md +214 -214
  33. package/docs/middleware.md +355 -355
  34. package/docs/rate-limiting.md +393 -393
  35. package/docs/routing.md +302 -302
  36. package/lib/llm/client.js +73 -0
  37. package/lib/llm/index.js +7 -0
  38. package/lib/llm/parse.js +89 -0
  39. package/package.json +64 -62
  40. package/rate-limit/algorithms/fixed-window.js +141 -141
  41. package/rate-limit/algorithms/sliding-window.js +147 -147
  42. package/rate-limit/algorithms/token-bucket.js +115 -115
  43. package/rate-limit/base.js +165 -165
  44. package/rate-limit/index.js +147 -147
  45. package/rate-limit/storage/base.js +104 -104
  46. package/rate-limit/storage/memory.js +101 -101
  47. package/rate-limit/storage/redis.js +88 -88
  48. package/server/ammo/body-parser.js +220 -220
  49. package/server/ammo/dispatch-helper.js +103 -103
  50. package/server/ammo/enhancer.js +57 -57
  51. package/server/ammo.js +454 -415
  52. package/server/endpoint.js +97 -74
  53. package/server/error.js +9 -9
  54. package/server/errors/code-context.js +125 -125
  55. package/server/errors/llm-error-service.js +140 -140
  56. package/server/files/helper.js +33 -33
  57. package/server/files/uploader.js +143 -143
  58. package/server/handler.js +158 -119
  59. package/server/target.js +185 -175
  60. package/server/targets/middleware-validator.js +22 -22
  61. package/server/targets/path-validator.js +21 -21
  62. package/server/targets/registry.js +160 -160
  63. package/server/targets/shoot-validator.js +21 -21
  64. package/te.js +428 -402
  65. package/utils/auto-register.js +17 -17
  66. package/utils/configuration.js +64 -64
  67. package/utils/errors-llm-config.js +84 -84
  68. package/utils/request-logger.js +43 -43
  69. package/utils/status-codes.js +82 -82
  70. package/utils/tejas-entrypoint-html.js +18 -18
@@ -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.0",
4
- "description": "A nodejs 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
- "server",
34
- "database",
35
- "rate-limit",
36
- "utils",
37
- "auto-docs",
38
- "README.md",
39
- "docs"
40
- ],
41
- "dependencies": {
42
- "ansi-colors": "^4.1.3",
43
- "filesize": "^10.1.1",
44
- "formidable": "^3.5.1",
45
- "mime": "^4.0.1",
46
- "statuses": "^2.0.1",
47
- "tej-env": "^1.1.3",
48
- "tej-logger": "^1.2.1"
49
- },
50
- "husky": {
51
- "hooks": {
52
- "pre-commit": "lint-staged",
53
- "pre-push": "npm test"
54
- }
55
- },
56
- "lint-staged": {
57
- "src/**/*.{js,jsx}": [
58
- "pretty-quick --staged",
59
- "eslint --fix"
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;