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.
- package/.cursor/plans/ai_native_framework_features_5bb1a20a.plan.md +234 -0
- package/.cursor/plans/auto_error_fix_agent_e68979c5.plan.md +356 -0
- package/.cursor/plans/tejas_framework_test_suite_5e3c6fad.plan.md +168 -0
- package/.prettierignore +31 -0
- package/README.md +156 -14
- package/auto-docs/analysis/handler-analyzer.js +58 -0
- package/auto-docs/analysis/source-resolver.js +101 -0
- package/auto-docs/constants.js +37 -0
- package/auto-docs/index.js +146 -0
- package/auto-docs/llm/index.js +6 -0
- package/auto-docs/llm/parse.js +88 -0
- package/auto-docs/llm/prompts.js +222 -0
- package/auto-docs/llm/provider.js +187 -0
- package/auto-docs/openapi/endpoint-processor.js +277 -0
- package/auto-docs/openapi/generator.js +107 -0
- package/auto-docs/openapi/level3.js +131 -0
- package/auto-docs/openapi/spec-builders.js +244 -0
- package/auto-docs/ui/docs-ui.js +186 -0
- package/auto-docs/utils/logger.js +17 -0
- package/auto-docs/utils/strip-usage.js +10 -0
- package/cli/docs-command.js +315 -0
- package/cli/fly-command.js +71 -0
- package/cli/index.js +57 -0
- package/database/index.js +163 -5
- package/database/mongodb.js +146 -0
- package/database/redis.js +201 -0
- package/docs/README.md +36 -0
- package/docs/ammo.md +362 -0
- package/docs/api-reference.md +489 -0
- package/docs/auto-docs.md +215 -0
- package/docs/cli.md +152 -0
- package/docs/configuration.md +233 -0
- package/docs/database.md +391 -0
- package/docs/error-handling.md +417 -0
- package/docs/file-uploads.md +334 -0
- package/docs/getting-started.md +181 -0
- package/docs/middleware.md +356 -0
- package/docs/rate-limiting.md +394 -0
- package/docs/routing.md +302 -0
- package/example/API_OVERVIEW.md +77 -0
- package/example/README.md +155 -0
- package/example/index.js +27 -2
- package/example/openapi.json +390 -0
- package/example/package.json +5 -2
- package/example/services/cache.service.js +25 -0
- package/example/services/user.service.js +42 -0
- package/example/start-redis.js +2 -0
- package/example/targets/cache.target.js +35 -0
- package/example/targets/index.target.js +11 -2
- package/example/targets/users.target.js +60 -0
- package/example/tejas.config.json +13 -1
- package/package.json +20 -5
- package/rate-limit/algorithms/fixed-window.js +141 -0
- package/rate-limit/algorithms/sliding-window.js +147 -0
- package/rate-limit/algorithms/token-bucket.js +115 -0
- package/rate-limit/base.js +165 -0
- package/rate-limit/index.js +147 -0
- package/rate-limit/storage/base.js +104 -0
- package/rate-limit/storage/memory.js +102 -0
- package/rate-limit/storage/redis.js +88 -0
- package/server/ammo/body-parser.js +152 -25
- package/server/ammo/enhancer.js +6 -2
- package/server/ammo.js +356 -327
- package/server/endpoint.js +21 -0
- package/server/handler.js +113 -87
- package/server/target.js +50 -9
- package/server/targets/registry.js +111 -6
- package/te.js +363 -137
- package/tests/auto-docs/handler-analyzer.test.js +44 -0
- package/tests/auto-docs/openapi-generator.test.js +103 -0
- package/tests/auto-docs/parse.test.js +63 -0
- package/tests/auto-docs/source-resolver.test.js +58 -0
- package/tests/helpers/index.js +37 -0
- package/tests/helpers/mock-http.js +342 -0
- package/tests/helpers/test-utils.js +446 -0
- package/tests/setup.test.js +148 -0
- package/utils/configuration.js +13 -10
- package/vitest.config.js +54 -0
- package/database/mongo.js +0 -67
- 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
|
+
|