te.js 1.3.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/.cursor/plans/ai_native_framework_features_5bb1a20a.plan.md +234 -0
  2. package/.cursor/plans/auto_error_fix_agent_e68979c5.plan.md +356 -0
  3. package/.cursor/plans/tejas_framework_test_suite_5e3c6fad.plan.md +168 -0
  4. package/.prettierignore +31 -0
  5. package/README.md +156 -14
  6. package/auto-docs/analysis/handler-analyzer.js +58 -0
  7. package/auto-docs/analysis/source-resolver.js +101 -0
  8. package/auto-docs/constants.js +37 -0
  9. package/auto-docs/index.js +146 -0
  10. package/auto-docs/llm/index.js +6 -0
  11. package/auto-docs/llm/parse.js +88 -0
  12. package/auto-docs/llm/prompts.js +222 -0
  13. package/auto-docs/llm/provider.js +187 -0
  14. package/auto-docs/openapi/endpoint-processor.js +277 -0
  15. package/auto-docs/openapi/generator.js +107 -0
  16. package/auto-docs/openapi/level3.js +131 -0
  17. package/auto-docs/openapi/spec-builders.js +244 -0
  18. package/auto-docs/ui/docs-ui.js +186 -0
  19. package/auto-docs/utils/logger.js +17 -0
  20. package/auto-docs/utils/strip-usage.js +10 -0
  21. package/cli/docs-command.js +315 -0
  22. package/cli/fly-command.js +71 -0
  23. package/cli/index.js +57 -0
  24. package/database/index.js +163 -5
  25. package/database/mongodb.js +146 -0
  26. package/database/redis.js +201 -0
  27. package/docs/README.md +36 -0
  28. package/docs/ammo.md +362 -0
  29. package/docs/api-reference.md +489 -0
  30. package/docs/auto-docs.md +215 -0
  31. package/docs/cli.md +152 -0
  32. package/docs/configuration.md +233 -0
  33. package/docs/database.md +391 -0
  34. package/docs/error-handling.md +417 -0
  35. package/docs/file-uploads.md +334 -0
  36. package/docs/getting-started.md +181 -0
  37. package/docs/middleware.md +356 -0
  38. package/docs/rate-limiting.md +394 -0
  39. package/docs/routing.md +302 -0
  40. package/example/API_OVERVIEW.md +77 -0
  41. package/example/README.md +155 -0
  42. package/example/index.js +27 -2
  43. package/example/openapi.json +390 -0
  44. package/example/package.json +5 -2
  45. package/example/services/cache.service.js +25 -0
  46. package/example/services/user.service.js +42 -0
  47. package/example/start-redis.js +2 -0
  48. package/example/targets/cache.target.js +35 -0
  49. package/example/targets/index.target.js +11 -2
  50. package/example/targets/users.target.js +60 -0
  51. package/example/tejas.config.json +13 -1
  52. package/package.json +20 -5
  53. package/rate-limit/algorithms/fixed-window.js +141 -0
  54. package/rate-limit/algorithms/sliding-window.js +147 -0
  55. package/rate-limit/algorithms/token-bucket.js +115 -0
  56. package/rate-limit/base.js +165 -0
  57. package/rate-limit/index.js +147 -0
  58. package/rate-limit/storage/base.js +104 -0
  59. package/rate-limit/storage/memory.js +102 -0
  60. package/rate-limit/storage/redis.js +88 -0
  61. package/server/ammo/body-parser.js +152 -25
  62. package/server/ammo/enhancer.js +6 -2
  63. package/server/ammo.js +356 -327
  64. package/server/endpoint.js +21 -0
  65. package/server/handler.js +113 -87
  66. package/server/target.js +50 -9
  67. package/server/targets/registry.js +111 -6
  68. package/te.js +363 -137
  69. package/tests/auto-docs/handler-analyzer.test.js +44 -0
  70. package/tests/auto-docs/openapi-generator.test.js +103 -0
  71. package/tests/auto-docs/parse.test.js +63 -0
  72. package/tests/auto-docs/source-resolver.test.js +58 -0
  73. package/tests/helpers/index.js +37 -0
  74. package/tests/helpers/mock-http.js +342 -0
  75. package/tests/helpers/test-utils.js +446 -0
  76. package/tests/setup.test.js +148 -0
  77. package/utils/configuration.js +13 -10
  78. package/vitest.config.js +54 -0
  79. package/database/mongo.js +0 -67
  80. package/example/targets/user/user.target.js +0 -17
@@ -0,0 +1,334 @@
1
+ # File Uploads
2
+
3
+ Tejas provides a built-in `TejFileUploader` class for handling file uploads with ease.
4
+
5
+ ## Quick Start
6
+
7
+ ```javascript
8
+ import { Target, TejFileUploader } from 'te.js';
9
+
10
+ const upload = new TejFileUploader({
11
+ destination: 'uploads/',
12
+ maxFileSize: 5 * 1024 * 1024 // 5MB
13
+ });
14
+
15
+ const target = new Target('/files');
16
+
17
+ target.register('/upload', upload.file('avatar'), (ammo) => {
18
+ ammo.fire({ file: ammo.payload.avatar });
19
+ });
20
+ ```
21
+
22
+ ## Configuration
23
+
24
+ ```javascript
25
+ const upload = new TejFileUploader({
26
+ destination: 'public/uploads', // Where to save files
27
+ name: 'custom-name', // Optional: custom filename
28
+ maxFileSize: 10 * 1024 * 1024 // Max file size in bytes (10MB)
29
+ });
30
+ ```
31
+
32
+ ### Options
33
+
34
+ | Option | Type | Description |
35
+ |--------|------|-------------|
36
+ | `destination` | string | Directory to save uploaded files |
37
+ | `name` | string | Optional custom filename |
38
+ | `maxFileSize` | number | Maximum file size in bytes |
39
+
40
+ ## Single File Upload
41
+
42
+ Use `upload.file()` for single file uploads:
43
+
44
+ ```javascript
45
+ // Expects a file field named 'avatar'
46
+ target.register('/avatar', upload.file('avatar'), (ammo) => {
47
+ const file = ammo.payload.avatar;
48
+
49
+ ammo.fire({
50
+ filename: file.filename,
51
+ path: file.path.relative,
52
+ mimetype: file.mimetype,
53
+ size: file.size
54
+ });
55
+ });
56
+ ```
57
+
58
+ ### File Object Structure
59
+
60
+ When a file is uploaded, `ammo.payload[fieldName]` contains:
61
+
62
+ ```javascript
63
+ {
64
+ filename: 'photo.jpg', // Original filename
65
+ extension: 'jpg', // File extension
66
+ path: {
67
+ absolute: '/var/www/uploads/photo.jpg', // Absolute path on disk
68
+ relative: '\\uploads\\photo.jpg' // Relative to cwd
69
+ },
70
+ mimetype: 'image/jpeg', // MIME type
71
+ size: { // From the filesize library
72
+ value: 245, // Numeric value
73
+ symbol: 'KB' // Unit (B, KB, MB, etc.)
74
+ }
75
+ }
76
+ ```
77
+
78
+ The `size` object is produced by the [filesize](https://www.npmjs.com/package/filesize) library. Use `${size.value} ${size.symbol}` for display (e.g. "245 KB").
79
+
80
+ ## Multiple File Upload
81
+
82
+ Use `upload.files()` for multiple files:
83
+
84
+ ```javascript
85
+ // Expects files in 'photos' and 'documents' fields
86
+ target.register('/documents', upload.files('photos', 'documents'), (ammo) => {
87
+ const { photos, documents } = ammo.payload;
88
+
89
+ ammo.fire({
90
+ photos: photos || [], // Array of file objects
91
+ documents: documents || [] // Array of file objects
92
+ });
93
+ });
94
+ ```
95
+
96
+ ### Multiple Files Response
97
+
98
+ Each field contains an array of file objects:
99
+
100
+ ```javascript
101
+ {
102
+ photos: [
103
+ { filename: 'photo1.jpg', path: {...}, mimetype: 'image/jpeg', size: {...} },
104
+ { filename: 'photo2.jpg', path: {...}, mimetype: 'image/jpeg', size: {...} }
105
+ ],
106
+ documents: [
107
+ { filename: 'doc.pdf', path: {...}, mimetype: 'application/pdf', size: {...} }
108
+ ]
109
+ }
110
+ ```
111
+
112
+ ## Mixed Fields (Files + Data)
113
+
114
+ File uploads can include regular form fields:
115
+
116
+ ```javascript
117
+ target.register('/profile', upload.file('avatar'), (ammo) => {
118
+ const { avatar, name, bio } = ammo.payload;
119
+
120
+ ammo.fire({
121
+ name, // Regular form field
122
+ bio, // Regular form field
123
+ avatar: avatar // File object
124
+ });
125
+ });
126
+ ```
127
+
128
+ ## File Size Limits
129
+
130
+ When a file exceeds `maxFileSize`, a `413 Payload Too Large` error is thrown automatically. The error message includes the human-readable limit (e.g. "File size exceeds 2 MB"):
131
+
132
+ ```javascript
133
+ const upload = new TejFileUploader({
134
+ destination: 'uploads/',
135
+ maxFileSize: 2 * 1024 * 1024 // 2MB limit
136
+ });
137
+
138
+ target.register('/upload', upload.file('file'), (ammo) => {
139
+ // If file > 2MB, this handler never runs
140
+ // Client receives: 413 "File size exceeds 2 MB"
141
+ ammo.fire({ success: true });
142
+ });
143
+ ```
144
+
145
+ Note that the overall request body is also subject to the global `body.max_size` limit (default 10 MB). See [Configuration](./configuration.md).
146
+
147
+ ## Client-Side Examples
148
+
149
+ ### HTML Form
150
+
151
+ ```html
152
+ <form action="/files/upload" method="POST" enctype="multipart/form-data">
153
+ <input type="file" name="avatar" />
154
+ <input type="text" name="username" />
155
+ <button type="submit">Upload</button>
156
+ </form>
157
+ ```
158
+
159
+ ### JavaScript (Fetch)
160
+
161
+ ```javascript
162
+ const formData = new FormData();
163
+ formData.append('avatar', fileInput.files[0]);
164
+ formData.append('username', 'john');
165
+
166
+ const response = await fetch('/files/upload', {
167
+ method: 'POST',
168
+ body: formData
169
+ });
170
+ ```
171
+
172
+ ### JavaScript (Multiple Files)
173
+
174
+ ```javascript
175
+ const formData = new FormData();
176
+
177
+ // Add multiple files to same field
178
+ for (const file of fileInput.files) {
179
+ formData.append('photos', file);
180
+ }
181
+
182
+ const response = await fetch('/files/documents', {
183
+ method: 'POST',
184
+ body: formData
185
+ });
186
+ ```
187
+
188
+ ## Complete Example
189
+
190
+ ```javascript
191
+ import { Target, TejFileUploader, TejError } from 'te.js';
192
+ import fs from 'fs';
193
+ import path from 'path';
194
+
195
+ const target = new Target('/api/files');
196
+
197
+ // Configure uploader
198
+ const imageUpload = new TejFileUploader({
199
+ destination: 'public/images',
200
+ maxFileSize: 5 * 1024 * 1024 // 5MB
201
+ });
202
+
203
+ const documentUpload = new TejFileUploader({
204
+ destination: 'private/documents',
205
+ maxFileSize: 20 * 1024 * 1024 // 20MB
206
+ });
207
+
208
+ // Upload profile image
209
+ target.register('/profile-image', imageUpload.file('image'), (ammo) => {
210
+ if (!ammo.POST) return ammo.notAllowed();
211
+
212
+ const { image } = ammo.payload;
213
+
214
+ if (!image) {
215
+ throw new TejError(400, 'No image provided');
216
+ }
217
+
218
+ // Validate image type
219
+ if (!image.mimetype.startsWith('image/')) {
220
+ // Delete uploaded file
221
+ fs.unlinkSync(image.path.absolute);
222
+ throw new TejError(400, 'File must be an image');
223
+ }
224
+
225
+ ammo.fire({
226
+ message: 'Profile image uploaded',
227
+ url: `/images/${image.filename}`
228
+ });
229
+ });
230
+
231
+ // Upload multiple documents
232
+ target.register('/documents', documentUpload.files('files'), (ammo) => {
233
+ if (!ammo.POST) return ammo.notAllowed();
234
+
235
+ const { files } = ammo.payload;
236
+
237
+ if (!files || files.length === 0) {
238
+ throw new TejError(400, 'No files provided');
239
+ }
240
+
241
+ ammo.fire({
242
+ message: `${files.length} files uploaded`,
243
+ files: files.map(f => ({
244
+ name: f.filename,
245
+ size: `${f.size.value} ${f.size.symbol}`
246
+ }))
247
+ });
248
+ });
249
+
250
+ // Delete a file
251
+ target.register('/delete/:filename', (ammo) => {
252
+ if (!ammo.DELETE) return ammo.notAllowed();
253
+
254
+ const { filename } = ammo.payload;
255
+ const filepath = path.join('public/images', filename);
256
+
257
+ if (!fs.existsSync(filepath)) {
258
+ throw new TejError(404, 'File not found');
259
+ }
260
+
261
+ fs.unlinkSync(filepath);
262
+ ammo.fire({ message: 'File deleted' });
263
+ });
264
+ ```
265
+
266
+ ## Validation Middleware
267
+
268
+ Create reusable validation middleware:
269
+
270
+ ```javascript
271
+ // middleware/validate-image.js
272
+ import { TejError } from 'te.js';
273
+ import fs from 'fs';
274
+
275
+ const allowedTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
276
+
277
+ export const validateImage = (fieldName) => (ammo, next) => {
278
+ const file = ammo.payload[fieldName];
279
+
280
+ if (!file) {
281
+ throw new TejError(400, `${fieldName} is required`);
282
+ }
283
+
284
+ if (!allowedTypes.includes(file.mimetype)) {
285
+ fs.unlinkSync(file.path.absolute);
286
+ throw new TejError(400, 'Only JPEG, PNG, GIF, and WebP images are allowed');
287
+ }
288
+
289
+ next();
290
+ };
291
+
292
+ // Usage
293
+ target.register('/avatar',
294
+ upload.file('avatar'),
295
+ validateImage('avatar'),
296
+ (ammo) => {
297
+ ammo.fire({ success: true });
298
+ }
299
+ );
300
+ ```
301
+
302
+ ## Serving Uploaded Files
303
+
304
+ Tejas doesn't include a static file server, but you can serve files manually:
305
+
306
+ ```javascript
307
+ import fs from 'fs';
308
+ import path from 'path';
309
+ import mime from 'mime';
310
+
311
+ target.register('/images/:filename', (ammo) => {
312
+ const { filename } = ammo.payload;
313
+ const filepath = path.join('public/images', filename);
314
+
315
+ if (!fs.existsSync(filepath)) {
316
+ return ammo.notFound();
317
+ }
318
+
319
+ const file = fs.readFileSync(filepath);
320
+ const contentType = mime.getType(filepath) || 'application/octet-stream';
321
+
322
+ ammo.fire(200, file, contentType);
323
+ });
324
+ ```
325
+
326
+ ## Best Practices
327
+
328
+ 1. **Validate file types** — Don't trust client-reported MIME types
329
+ 2. **Set size limits** — Prevent disk exhaustion attacks
330
+ 3. **Use unique filenames** — Avoid overwrites with UUID or timestamps
331
+ 4. **Store outside web root** — For sensitive files, store in private directories
332
+ 5. **Clean up on errors** — Delete uploaded files if validation fails
333
+ 6. **Scan for malware** — For production systems, integrate virus scanning
334
+
@@ -0,0 +1,181 @@
1
+ # Getting Started with Tejas
2
+
3
+ Tejas is a lightweight Node.js framework for building powerful backend services. It features an intuitive API with aviation-inspired naming conventions.
4
+
5
+ ## Why Tejas?
6
+
7
+ - **Zero-Config Error Handling** — No try-catch needed! Tejas catches all errors automatically
8
+ - **Clean, Readable Code** — Aviation-inspired naming makes code self-documenting
9
+ - **Express Compatible** — Use your existing Express middleware
10
+ - **Built-in Features** — Rate limiting, file uploads, database connections out of the box
11
+
12
+ ## Prerequisites
13
+
14
+ - Node.js 18.x or higher
15
+ - npm or yarn
16
+
17
+ ## Installation
18
+
19
+ ```bash
20
+ npm install te.js
21
+ ```
22
+
23
+ ## Quick Start
24
+
25
+ ### 1. Create Your Application
26
+
27
+ Create an `index.js` file:
28
+
29
+ ```javascript
30
+ import Tejas from 'te.js';
31
+
32
+ const app = new Tejas();
33
+
34
+ app.takeoff();
35
+ ```
36
+
37
+ ### 2. Create Your First Route
38
+
39
+ Create a `targets` directory and add `hello.target.js`:
40
+
41
+ ```javascript
42
+ import { Target } from 'te.js';
43
+
44
+ const target = new Target('/hello');
45
+
46
+ target.register('/', (ammo) => {
47
+ ammo.fire({ message: 'Hello, World!' });
48
+ });
49
+
50
+ target.register('/greet/:name', (ammo) => {
51
+ const { name } = ammo.payload;
52
+ ammo.fire({ message: `Hello, ${name}!` });
53
+ });
54
+ ```
55
+
56
+ ### 3. Run Your Application
57
+
58
+ ```bash
59
+ node index.js
60
+ ```
61
+
62
+ Your server is now running on `http://localhost:1403`
63
+
64
+ ## Core Concepts
65
+
66
+ ### Terminology
67
+
68
+ Tejas uses aviation-inspired naming:
69
+
70
+ | Term | Express Equivalent | Description |
71
+ |------|-------------------|-------------|
72
+ | `Tejas` | `express()` | Main application instance |
73
+ | `Target` | `Router` | Route grouping |
74
+ | `Ammo` | `req` + `res` | Request/response wrapper |
75
+ | `fire()` | `res.send()` | Send response |
76
+ | `throw()` | Error response | Send error |
77
+ | `midair()` | `use()` | Register middleware |
78
+ | `takeoff()` | `listen()` | Start server |
79
+
80
+ ### Basic Structure
81
+
82
+ ```
83
+ my-app/
84
+ ├── index.js # Application entry point
85
+ ├── tejas.config.json # Optional configuration
86
+ ├── .env # Environment variables
87
+ ├── targets/ # Route definitions (auto-discovered)
88
+ │ ├── user.target.js
89
+ │ ├── auth.target.js
90
+ │ └── api/
91
+ │ └── v1.target.js
92
+ ├── services/ # Business logic
93
+ │ └── user.service.js
94
+ └── middleware/ # Custom middleware
95
+ └── auth.js
96
+ ```
97
+
98
+ ## Automatic Error Handling
99
+
100
+ One of Tejas's most powerful features is that **you don't need to write any error handling code**. The framework catches all errors automatically:
101
+
102
+ ```javascript
103
+ // ✅ No try-catch needed — if anything throws, Tejas handles it
104
+ target.register('/data', async (ammo) => {
105
+ const data = await riskyDatabaseCall();
106
+ const processed = await anotherAsyncOperation(data);
107
+ ammo.fire(processed);
108
+ });
109
+ ```
110
+
111
+ Your application never crashes from unhandled exceptions, and clients always receive proper error responses. Learn more in [Error Handling](./error-handling.md).
112
+
113
+ ## Next Steps
114
+
115
+ - [Configuration](./configuration.md) — All configuration options and sources
116
+ - [Routing](./routing.md) — Deep dive into the Target-based routing system
117
+ - [Ammo](./ammo.md) — Master request/response handling
118
+ - [Middleware](./middleware.md) — Global, target, and route-level middleware
119
+ - [Database](./database.md) — Connect to MongoDB or Redis
120
+ - [Error Handling](./error-handling.md) — Zero-config error handling
121
+ - [CLI Reference](./cli.md) — `tejas fly` and doc generation commands
122
+ - [Auto-Documentation](./auto-docs.md) — Generate OpenAPI specs from your code
123
+
124
+ ## Example Application
125
+
126
+ Here's a more complete example:
127
+
128
+ ```javascript
129
+ import Tejas from 'te.js';
130
+
131
+ const app = new Tejas({
132
+ port: 3000,
133
+ log: {
134
+ http_requests: true,
135
+ exceptions: true
136
+ }
137
+ });
138
+
139
+ // Global middleware
140
+ app.midair((ammo, next) => {
141
+ console.log(`${ammo.method} ${ammo.path}`);
142
+ next();
143
+ });
144
+
145
+ // Rate limiting (in-memory)
146
+ app.withRateLimit({
147
+ maxRequests: 100,
148
+ timeWindowSeconds: 60
149
+ });
150
+
151
+ // Start with optional Redis
152
+ app.takeoff({
153
+ withRedis: { url: 'redis://localhost:6379' }
154
+ });
155
+ ```
156
+
157
+ ```javascript
158
+ // targets/api.target.js
159
+ import { Target } from 'te.js';
160
+
161
+ const api = new Target('/api');
162
+
163
+ // GET /api/status
164
+ api.register('/status', (ammo) => {
165
+ if (ammo.GET) {
166
+ ammo.fire({ status: 'operational', timestamp: Date.now() });
167
+ } else {
168
+ ammo.notAllowed();
169
+ }
170
+ });
171
+
172
+ // POST /api/echo
173
+ api.register('/echo', (ammo) => {
174
+ if (ammo.POST) {
175
+ ammo.fire(ammo.payload);
176
+ } else {
177
+ ammo.notAllowed();
178
+ }
179
+ });
180
+ ```
181
+