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/docs/routing.md
CHANGED
|
@@ -1,302 +1,302 @@
|
|
|
1
|
-
# Routing with Targets
|
|
2
|
-
|
|
3
|
-
Tejas uses a **Target-based** routing system. A Target is similar to an Express Router—it groups related endpoints under a common base path.
|
|
4
|
-
|
|
5
|
-
## Creating a Target
|
|
6
|
-
|
|
7
|
-
```javascript
|
|
8
|
-
import { Target } from 'te.js';
|
|
9
|
-
|
|
10
|
-
const target = new Target('/api');
|
|
11
|
-
```
|
|
12
|
-
|
|
13
|
-
## Registering Endpoints
|
|
14
|
-
|
|
15
|
-
Use `register()` to add endpoints to a target:
|
|
16
|
-
|
|
17
|
-
```javascript
|
|
18
|
-
target.register('/users', (ammo) => {
|
|
19
|
-
ammo.fire([{ id: 1, name: 'John' }]);
|
|
20
|
-
});
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
This creates a route at `GET /api/users`.
|
|
24
|
-
|
|
25
|
-
## Method Handling
|
|
26
|
-
|
|
27
|
-
Tejas routes are **method-agnostic** by default. Use the method flags on `ammo` to handle different HTTP methods:
|
|
28
|
-
|
|
29
|
-
```javascript
|
|
30
|
-
target.register('/users', (ammo) => {
|
|
31
|
-
if (ammo.GET) {
|
|
32
|
-
// Handle GET /api/users
|
|
33
|
-
ammo.fire([{ id: 1, name: 'John' }]);
|
|
34
|
-
} else if (ammo.POST) {
|
|
35
|
-
// Handle POST /api/users
|
|
36
|
-
const { name, email } = ammo.payload;
|
|
37
|
-
ammo.fire(201, { id: 2, name, email });
|
|
38
|
-
} else {
|
|
39
|
-
ammo.notAllowed();
|
|
40
|
-
}
|
|
41
|
-
});
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
### Available Method Flags
|
|
45
|
-
|
|
46
|
-
- `ammo.GET`
|
|
47
|
-
- `ammo.POST`
|
|
48
|
-
- `ammo.PUT`
|
|
49
|
-
- `ammo.DELETE`
|
|
50
|
-
- `ammo.PATCH`
|
|
51
|
-
- `ammo.HEAD`
|
|
52
|
-
- `ammo.OPTIONS`
|
|
53
|
-
|
|
54
|
-
## Parameterized Routes
|
|
55
|
-
|
|
56
|
-
Use `:param` syntax for dynamic route segments:
|
|
57
|
-
|
|
58
|
-
```javascript
|
|
59
|
-
target.register('/users/:id', (ammo) => {
|
|
60
|
-
const { id } = ammo.payload;
|
|
61
|
-
ammo.fire({ userId: id });
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
target.register('/users/:userId/posts/:postId', (ammo) => {
|
|
65
|
-
const { userId, postId } = ammo.payload;
|
|
66
|
-
ammo.fire({ userId, postId });
|
|
67
|
-
});
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
Route parameters are automatically extracted and added to `ammo.payload`.
|
|
71
|
-
|
|
72
|
-
## Query Parameters
|
|
73
|
-
|
|
74
|
-
Query parameters are also available in `ammo.payload`:
|
|
75
|
-
|
|
76
|
-
```javascript
|
|
77
|
-
// Request: GET /api/users?page=2&limit=10
|
|
78
|
-
|
|
79
|
-
target.register('/users', (ammo) => {
|
|
80
|
-
const { page, limit } = ammo.payload;
|
|
81
|
-
ammo.fire({ page, limit }); // { page: "2", limit: "10" }
|
|
82
|
-
});
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
## Route Priority
|
|
86
|
-
|
|
87
|
-
Routes are matched in the following order:
|
|
88
|
-
|
|
89
|
-
1. **Exact matches** (most specific)
|
|
90
|
-
2. **Parameterized routes** (in registration order)
|
|
91
|
-
|
|
92
|
-
```javascript
|
|
93
|
-
// These don't conflict:
|
|
94
|
-
target.register('/users/me', handler); // Exact match for /users/me
|
|
95
|
-
target.register('/users/:id', handler); // Matches /users/123, /users/john
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
## Target-Level Middleware
|
|
99
|
-
|
|
100
|
-
Apply middleware to all routes in a target:
|
|
101
|
-
|
|
102
|
-
```javascript
|
|
103
|
-
const api = new Target('/api');
|
|
104
|
-
|
|
105
|
-
// This middleware runs for ALL /api/* routes
|
|
106
|
-
api.midair((ammo, next) => {
|
|
107
|
-
console.log('API request:', ammo.path);
|
|
108
|
-
next();
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
api.register('/users', handler);
|
|
112
|
-
api.register('/posts', handler);
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
## Route-Specific Middleware
|
|
116
|
-
|
|
117
|
-
Apply middleware to individual routes:
|
|
118
|
-
|
|
119
|
-
```javascript
|
|
120
|
-
import { authMiddleware } from './middleware/auth.js';
|
|
121
|
-
|
|
122
|
-
target.register('/public', (ammo) => {
|
|
123
|
-
ammo.fire({ public: true });
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
// Auth middleware only for this route
|
|
127
|
-
target.register('/private', authMiddleware, (ammo) => {
|
|
128
|
-
ammo.fire({ private: true, user: ammo.user });
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
// Multiple middleware
|
|
132
|
-
target.register('/admin', authMiddleware, adminMiddleware, (ammo) => {
|
|
133
|
-
ammo.fire({ admin: true });
|
|
134
|
-
});
|
|
135
|
-
```
|
|
136
|
-
|
|
137
|
-
## File Organization
|
|
138
|
-
|
|
139
|
-
### Recommended Structure
|
|
140
|
-
|
|
141
|
-
```
|
|
142
|
-
targets/
|
|
143
|
-
├── index.target.js # Root routes (/)
|
|
144
|
-
├── user.target.js # User routes (/user)
|
|
145
|
-
├── auth.target.js # Auth routes (/auth)
|
|
146
|
-
└── api/
|
|
147
|
-
├── v1.target.js # API v1 routes (/api/v1)
|
|
148
|
-
└── v2.target.js # API v2 routes (/api/v2)
|
|
149
|
-
```
|
|
150
|
-
|
|
151
|
-
### Example: user.target.js
|
|
152
|
-
|
|
153
|
-
```javascript
|
|
154
|
-
import { Target } from 'te.js';
|
|
155
|
-
import { authMiddleware } from '../middleware/auth.js';
|
|
156
|
-
|
|
157
|
-
const users = new Target('/users');
|
|
158
|
-
|
|
159
|
-
// Public route
|
|
160
|
-
users.register('/register', (ammo) => {
|
|
161
|
-
if (!ammo.POST) return ammo.notAllowed();
|
|
162
|
-
|
|
163
|
-
const { email, password, name } = ammo.payload;
|
|
164
|
-
// ... create user
|
|
165
|
-
ammo.fire(201, { message: 'User created' });
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
// Protected routes
|
|
169
|
-
users.midair(authMiddleware);
|
|
170
|
-
|
|
171
|
-
users.register('/profile', (ammo) => {
|
|
172
|
-
if (ammo.GET) {
|
|
173
|
-
ammo.fire({ user: ammo.user });
|
|
174
|
-
} else if (ammo.PUT) {
|
|
175
|
-
// ... update profile
|
|
176
|
-
ammo.fire({ message: 'Profile updated' });
|
|
177
|
-
} else {
|
|
178
|
-
ammo.notAllowed();
|
|
179
|
-
}
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
users.register('/:id', (ammo) => {
|
|
183
|
-
if (!ammo.GET) return ammo.notAllowed();
|
|
184
|
-
|
|
185
|
-
const { id } = ammo.payload;
|
|
186
|
-
// ... fetch user by id
|
|
187
|
-
ammo.fire({ id, name: 'John Doe' });
|
|
188
|
-
});
|
|
189
|
-
```
|
|
190
|
-
|
|
191
|
-
## Endpoint Metadata
|
|
192
|
-
|
|
193
|
-
You can optionally pass a metadata object as the second argument to `register()`. This metadata is used by the [auto-documentation](./auto-docs.md) system to generate richer OpenAPI specs:
|
|
194
|
-
|
|
195
|
-
```javascript
|
|
196
|
-
target.register('/users', {
|
|
197
|
-
summary: 'User operations',
|
|
198
|
-
description: 'Create and list users',
|
|
199
|
-
methods: ['GET', 'POST'],
|
|
200
|
-
request: {
|
|
201
|
-
name: { type: 'string', required: true },
|
|
202
|
-
email: { type: 'string', required: true }
|
|
203
|
-
},
|
|
204
|
-
response: {
|
|
205
|
-
200: { description: 'User list' },
|
|
206
|
-
201: { description: 'User created' }
|
|
207
|
-
}
|
|
208
|
-
}, (ammo) => {
|
|
209
|
-
if (ammo.GET) return ammo.fire(getUsers());
|
|
210
|
-
if (ammo.POST) return ammo.fire(201, createUser(ammo.payload));
|
|
211
|
-
ammo.notAllowed();
|
|
212
|
-
});
|
|
213
|
-
```
|
|
214
|
-
|
|
215
|
-
When metadata is omitted, the auto-docs LLM infers everything from the handler source code.
|
|
216
|
-
|
|
217
|
-
## Method-Agnostic Handlers
|
|
218
|
-
|
|
219
|
-
If a handler does not check any method flags (`ammo.GET`, `ammo.POST`, etc.), it is treated as accepting **all HTTP methods**. This is useful for simple endpoints:
|
|
220
|
-
|
|
221
|
-
```javascript
|
|
222
|
-
target.register('/health', (ammo) => {
|
|
223
|
-
ammo.fire({ status: 'ok' });
|
|
224
|
-
});
|
|
225
|
-
```
|
|
226
|
-
|
|
227
|
-
## Listing All Routes
|
|
228
|
-
|
|
229
|
-
Get all registered endpoints programmatically:
|
|
230
|
-
|
|
231
|
-
```javascript
|
|
232
|
-
import { listAllEndpoints } from 'te.js';
|
|
233
|
-
|
|
234
|
-
// Get flat list of paths
|
|
235
|
-
const routes = listAllEndpoints();
|
|
236
|
-
// ['/api/users', '/api/posts', '/auth/login', ...]
|
|
237
|
-
|
|
238
|
-
// Get grouped by first path segment
|
|
239
|
-
const grouped = listAllEndpoints(true);
|
|
240
|
-
// {
|
|
241
|
-
// api: ['/api/users', '/api/posts'],
|
|
242
|
-
// auth: ['/auth/login', '/auth/register']
|
|
243
|
-
// }
|
|
244
|
-
```
|
|
245
|
-
|
|
246
|
-
## Complete Example
|
|
247
|
-
|
|
248
|
-
```javascript
|
|
249
|
-
// targets/products.target.js
|
|
250
|
-
import { Target, TejError } from 'te.js';
|
|
251
|
-
import { authMiddleware, adminMiddleware } from '../middleware/index.js';
|
|
252
|
-
|
|
253
|
-
const products = new Target('/products');
|
|
254
|
-
|
|
255
|
-
// Public: List all products
|
|
256
|
-
products.register('/', (ammo) => {
|
|
257
|
-
if (!ammo.GET) return ammo.notAllowed();
|
|
258
|
-
|
|
259
|
-
const { category, page = 1, limit = 10 } = ammo.payload;
|
|
260
|
-
// ... fetch products
|
|
261
|
-
ammo.fire({ products: [], page, limit });
|
|
262
|
-
});
|
|
263
|
-
|
|
264
|
-
// Public: Get single product
|
|
265
|
-
products.register('/:id', (ammo) => {
|
|
266
|
-
if (!ammo.GET) return ammo.notAllowed();
|
|
267
|
-
|
|
268
|
-
const { id } = ammo.payload;
|
|
269
|
-
const product = findProduct(id);
|
|
270
|
-
|
|
271
|
-
if (!product) {
|
|
272
|
-
throw new TejError(404, 'Product not found');
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
ammo.fire(product);
|
|
276
|
-
});
|
|
277
|
-
|
|
278
|
-
// Protected: Create product (admin only)
|
|
279
|
-
products.register('/create', authMiddleware, adminMiddleware, (ammo) => {
|
|
280
|
-
if (!ammo.POST) return ammo.notAllowed();
|
|
281
|
-
|
|
282
|
-
const { name, price, description } = ammo.payload;
|
|
283
|
-
// ... create product
|
|
284
|
-
ammo.fire(201, { id: 'new-id', name, price });
|
|
285
|
-
});
|
|
286
|
-
|
|
287
|
-
// Protected: Update product (admin only)
|
|
288
|
-
products.register('/:id/update', authMiddleware, adminMiddleware, (ammo) => {
|
|
289
|
-
if (!ammo.PUT) return ammo.notAllowed();
|
|
290
|
-
|
|
291
|
-
const { id, ...updates } = ammo.payload;
|
|
292
|
-
// ... update product
|
|
293
|
-
ammo.fire({ message: 'Product updated' });
|
|
294
|
-
});
|
|
295
|
-
```
|
|
296
|
-
|
|
297
|
-
## Next Steps
|
|
298
|
-
|
|
299
|
-
- [Ammo](./ammo.md) — Handle requests and send responses
|
|
300
|
-
- [Middleware](./middleware.md) — Global, target, and route-level middleware
|
|
301
|
-
- [Auto-Documentation](./auto-docs.md) — Endpoint metadata for OpenAPI generation
|
|
302
|
-
|
|
1
|
+
# Routing with Targets
|
|
2
|
+
|
|
3
|
+
Tejas uses a **Target-based** routing system. A Target is similar to an Express Router—it groups related endpoints under a common base path.
|
|
4
|
+
|
|
5
|
+
## Creating a Target
|
|
6
|
+
|
|
7
|
+
```javascript
|
|
8
|
+
import { Target } from 'te.js';
|
|
9
|
+
|
|
10
|
+
const target = new Target('/api');
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Registering Endpoints
|
|
14
|
+
|
|
15
|
+
Use `register()` to add endpoints to a target:
|
|
16
|
+
|
|
17
|
+
```javascript
|
|
18
|
+
target.register('/users', (ammo) => {
|
|
19
|
+
ammo.fire([{ id: 1, name: 'John' }]);
|
|
20
|
+
});
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
This creates a route at `GET /api/users`.
|
|
24
|
+
|
|
25
|
+
## Method Handling
|
|
26
|
+
|
|
27
|
+
Tejas routes are **method-agnostic** by default. Use the method flags on `ammo` to handle different HTTP methods:
|
|
28
|
+
|
|
29
|
+
```javascript
|
|
30
|
+
target.register('/users', (ammo) => {
|
|
31
|
+
if (ammo.GET) {
|
|
32
|
+
// Handle GET /api/users
|
|
33
|
+
ammo.fire([{ id: 1, name: 'John' }]);
|
|
34
|
+
} else if (ammo.POST) {
|
|
35
|
+
// Handle POST /api/users
|
|
36
|
+
const { name, email } = ammo.payload;
|
|
37
|
+
ammo.fire(201, { id: 2, name, email });
|
|
38
|
+
} else {
|
|
39
|
+
ammo.notAllowed();
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Available Method Flags
|
|
45
|
+
|
|
46
|
+
- `ammo.GET`
|
|
47
|
+
- `ammo.POST`
|
|
48
|
+
- `ammo.PUT`
|
|
49
|
+
- `ammo.DELETE`
|
|
50
|
+
- `ammo.PATCH`
|
|
51
|
+
- `ammo.HEAD`
|
|
52
|
+
- `ammo.OPTIONS`
|
|
53
|
+
|
|
54
|
+
## Parameterized Routes
|
|
55
|
+
|
|
56
|
+
Use `:param` syntax for dynamic route segments:
|
|
57
|
+
|
|
58
|
+
```javascript
|
|
59
|
+
target.register('/users/:id', (ammo) => {
|
|
60
|
+
const { id } = ammo.payload;
|
|
61
|
+
ammo.fire({ userId: id });
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
target.register('/users/:userId/posts/:postId', (ammo) => {
|
|
65
|
+
const { userId, postId } = ammo.payload;
|
|
66
|
+
ammo.fire({ userId, postId });
|
|
67
|
+
});
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Route parameters are automatically extracted and added to `ammo.payload`.
|
|
71
|
+
|
|
72
|
+
## Query Parameters
|
|
73
|
+
|
|
74
|
+
Query parameters are also available in `ammo.payload`:
|
|
75
|
+
|
|
76
|
+
```javascript
|
|
77
|
+
// Request: GET /api/users?page=2&limit=10
|
|
78
|
+
|
|
79
|
+
target.register('/users', (ammo) => {
|
|
80
|
+
const { page, limit } = ammo.payload;
|
|
81
|
+
ammo.fire({ page, limit }); // { page: "2", limit: "10" }
|
|
82
|
+
});
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Route Priority
|
|
86
|
+
|
|
87
|
+
Routes are matched in the following order:
|
|
88
|
+
|
|
89
|
+
1. **Exact matches** (most specific)
|
|
90
|
+
2. **Parameterized routes** (in registration order)
|
|
91
|
+
|
|
92
|
+
```javascript
|
|
93
|
+
// These don't conflict:
|
|
94
|
+
target.register('/users/me', handler); // Exact match for /users/me
|
|
95
|
+
target.register('/users/:id', handler); // Matches /users/123, /users/john
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Target-Level Middleware
|
|
99
|
+
|
|
100
|
+
Apply middleware to all routes in a target:
|
|
101
|
+
|
|
102
|
+
```javascript
|
|
103
|
+
const api = new Target('/api');
|
|
104
|
+
|
|
105
|
+
// This middleware runs for ALL /api/* routes
|
|
106
|
+
api.midair((ammo, next) => {
|
|
107
|
+
console.log('API request:', ammo.path);
|
|
108
|
+
next();
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
api.register('/users', handler);
|
|
112
|
+
api.register('/posts', handler);
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Route-Specific Middleware
|
|
116
|
+
|
|
117
|
+
Apply middleware to individual routes:
|
|
118
|
+
|
|
119
|
+
```javascript
|
|
120
|
+
import { authMiddleware } from './middleware/auth.js';
|
|
121
|
+
|
|
122
|
+
target.register('/public', (ammo) => {
|
|
123
|
+
ammo.fire({ public: true });
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
// Auth middleware only for this route
|
|
127
|
+
target.register('/private', authMiddleware, (ammo) => {
|
|
128
|
+
ammo.fire({ private: true, user: ammo.user });
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
// Multiple middleware
|
|
132
|
+
target.register('/admin', authMiddleware, adminMiddleware, (ammo) => {
|
|
133
|
+
ammo.fire({ admin: true });
|
|
134
|
+
});
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## File Organization
|
|
138
|
+
|
|
139
|
+
### Recommended Structure
|
|
140
|
+
|
|
141
|
+
```
|
|
142
|
+
targets/
|
|
143
|
+
├── index.target.js # Root routes (/)
|
|
144
|
+
├── user.target.js # User routes (/user)
|
|
145
|
+
├── auth.target.js # Auth routes (/auth)
|
|
146
|
+
└── api/
|
|
147
|
+
├── v1.target.js # API v1 routes (/api/v1)
|
|
148
|
+
└── v2.target.js # API v2 routes (/api/v2)
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### Example: user.target.js
|
|
152
|
+
|
|
153
|
+
```javascript
|
|
154
|
+
import { Target } from 'te.js';
|
|
155
|
+
import { authMiddleware } from '../middleware/auth.js';
|
|
156
|
+
|
|
157
|
+
const users = new Target('/users');
|
|
158
|
+
|
|
159
|
+
// Public route
|
|
160
|
+
users.register('/register', (ammo) => {
|
|
161
|
+
if (!ammo.POST) return ammo.notAllowed();
|
|
162
|
+
|
|
163
|
+
const { email, password, name } = ammo.payload;
|
|
164
|
+
// ... create user
|
|
165
|
+
ammo.fire(201, { message: 'User created' });
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
// Protected routes
|
|
169
|
+
users.midair(authMiddleware);
|
|
170
|
+
|
|
171
|
+
users.register('/profile', (ammo) => {
|
|
172
|
+
if (ammo.GET) {
|
|
173
|
+
ammo.fire({ user: ammo.user });
|
|
174
|
+
} else if (ammo.PUT) {
|
|
175
|
+
// ... update profile
|
|
176
|
+
ammo.fire({ message: 'Profile updated' });
|
|
177
|
+
} else {
|
|
178
|
+
ammo.notAllowed();
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
users.register('/:id', (ammo) => {
|
|
183
|
+
if (!ammo.GET) return ammo.notAllowed();
|
|
184
|
+
|
|
185
|
+
const { id } = ammo.payload;
|
|
186
|
+
// ... fetch user by id
|
|
187
|
+
ammo.fire({ id, name: 'John Doe' });
|
|
188
|
+
});
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
## Endpoint Metadata
|
|
192
|
+
|
|
193
|
+
You can optionally pass a metadata object as the second argument to `register()`. This metadata is used by the [auto-documentation](./auto-docs.md) system to generate richer OpenAPI specs:
|
|
194
|
+
|
|
195
|
+
```javascript
|
|
196
|
+
target.register('/users', {
|
|
197
|
+
summary: 'User operations',
|
|
198
|
+
description: 'Create and list users',
|
|
199
|
+
methods: ['GET', 'POST'],
|
|
200
|
+
request: {
|
|
201
|
+
name: { type: 'string', required: true },
|
|
202
|
+
email: { type: 'string', required: true }
|
|
203
|
+
},
|
|
204
|
+
response: {
|
|
205
|
+
200: { description: 'User list' },
|
|
206
|
+
201: { description: 'User created' }
|
|
207
|
+
}
|
|
208
|
+
}, (ammo) => {
|
|
209
|
+
if (ammo.GET) return ammo.fire(getUsers());
|
|
210
|
+
if (ammo.POST) return ammo.fire(201, createUser(ammo.payload));
|
|
211
|
+
ammo.notAllowed();
|
|
212
|
+
});
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
When metadata is omitted, the auto-docs LLM infers everything from the handler source code.
|
|
216
|
+
|
|
217
|
+
## Method-Agnostic Handlers
|
|
218
|
+
|
|
219
|
+
If a handler does not check any method flags (`ammo.GET`, `ammo.POST`, etc.), it is treated as accepting **all HTTP methods**. This is useful for simple endpoints:
|
|
220
|
+
|
|
221
|
+
```javascript
|
|
222
|
+
target.register('/health', (ammo) => {
|
|
223
|
+
ammo.fire({ status: 'ok' });
|
|
224
|
+
});
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
## Listing All Routes
|
|
228
|
+
|
|
229
|
+
Get all registered endpoints programmatically:
|
|
230
|
+
|
|
231
|
+
```javascript
|
|
232
|
+
import { listAllEndpoints } from 'te.js';
|
|
233
|
+
|
|
234
|
+
// Get flat list of paths
|
|
235
|
+
const routes = listAllEndpoints();
|
|
236
|
+
// ['/api/users', '/api/posts', '/auth/login', ...]
|
|
237
|
+
|
|
238
|
+
// Get grouped by first path segment
|
|
239
|
+
const grouped = listAllEndpoints(true);
|
|
240
|
+
// {
|
|
241
|
+
// api: ['/api/users', '/api/posts'],
|
|
242
|
+
// auth: ['/auth/login', '/auth/register']
|
|
243
|
+
// }
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
## Complete Example
|
|
247
|
+
|
|
248
|
+
```javascript
|
|
249
|
+
// targets/products.target.js
|
|
250
|
+
import { Target, TejError } from 'te.js';
|
|
251
|
+
import { authMiddleware, adminMiddleware } from '../middleware/index.js';
|
|
252
|
+
|
|
253
|
+
const products = new Target('/products');
|
|
254
|
+
|
|
255
|
+
// Public: List all products
|
|
256
|
+
products.register('/', (ammo) => {
|
|
257
|
+
if (!ammo.GET) return ammo.notAllowed();
|
|
258
|
+
|
|
259
|
+
const { category, page = 1, limit = 10 } = ammo.payload;
|
|
260
|
+
// ... fetch products
|
|
261
|
+
ammo.fire({ products: [], page, limit });
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
// Public: Get single product
|
|
265
|
+
products.register('/:id', (ammo) => {
|
|
266
|
+
if (!ammo.GET) return ammo.notAllowed();
|
|
267
|
+
|
|
268
|
+
const { id } = ammo.payload;
|
|
269
|
+
const product = findProduct(id);
|
|
270
|
+
|
|
271
|
+
if (!product) {
|
|
272
|
+
throw new TejError(404, 'Product not found');
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
ammo.fire(product);
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
// Protected: Create product (admin only)
|
|
279
|
+
products.register('/create', authMiddleware, adminMiddleware, (ammo) => {
|
|
280
|
+
if (!ammo.POST) return ammo.notAllowed();
|
|
281
|
+
|
|
282
|
+
const { name, price, description } = ammo.payload;
|
|
283
|
+
// ... create product
|
|
284
|
+
ammo.fire(201, { id: 'new-id', name, price });
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
// Protected: Update product (admin only)
|
|
288
|
+
products.register('/:id/update', authMiddleware, adminMiddleware, (ammo) => {
|
|
289
|
+
if (!ammo.PUT) return ammo.notAllowed();
|
|
290
|
+
|
|
291
|
+
const { id, ...updates } = ammo.payload;
|
|
292
|
+
// ... update product
|
|
293
|
+
ammo.fire({ message: 'Product updated' });
|
|
294
|
+
});
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
## Next Steps
|
|
298
|
+
|
|
299
|
+
- [Ammo](./ammo.md) — Handle requests and send responses
|
|
300
|
+
- [Middleware](./middleware.md) — Global, target, and route-level middleware
|
|
301
|
+
- [Auto-Documentation](./auto-docs.md) — Endpoint metadata for OpenAPI generation
|
|
302
|
+
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic OpenAI-compatible LLM client for te.js.
|
|
3
|
+
* POSTs to {baseURL}/chat/completions; used by auto-docs, error-inference, and future LLM features.
|
|
4
|
+
* No provider-specific npm dependencies — uses fetch() only.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const DEFAULT_BASE_URL = 'https://api.openai.com/v1';
|
|
8
|
+
const DEFAULT_MODEL = 'gpt-4o-mini';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* OpenAI-compatible LLM provider. Exposes only constructor and analyze(prompt).
|
|
12
|
+
*/
|
|
13
|
+
class LLMProvider {
|
|
14
|
+
constructor(options = {}) {
|
|
15
|
+
this.baseURL = (options.baseURL ?? DEFAULT_BASE_URL).replace(/\/$/, '');
|
|
16
|
+
this.model = options.model ?? DEFAULT_MODEL;
|
|
17
|
+
this.apiKey = options.apiKey ?? process.env.OPENAI_API_KEY;
|
|
18
|
+
this.options = options;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Send a prompt to the LLM and return the raw text response and usage.
|
|
23
|
+
* @param {string} prompt
|
|
24
|
+
* @returns {Promise<{ content: string, usage: { prompt_tokens: number, completion_tokens: number, total_tokens: number } }>}
|
|
25
|
+
*/
|
|
26
|
+
async analyze(prompt) {
|
|
27
|
+
const url = `${this.baseURL}/chat/completions`;
|
|
28
|
+
const headers = {
|
|
29
|
+
'Content-Type': 'application/json',
|
|
30
|
+
...(this.apiKey && { Authorization: `Bearer ${this.apiKey}` }),
|
|
31
|
+
};
|
|
32
|
+
const body = {
|
|
33
|
+
model: this.model,
|
|
34
|
+
messages: [{ role: 'user', content: prompt }],
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const res = await fetch(url, {
|
|
38
|
+
method: 'POST',
|
|
39
|
+
headers,
|
|
40
|
+
body: JSON.stringify(body),
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
if (!res.ok) {
|
|
44
|
+
const text = await res.text();
|
|
45
|
+
throw new Error(`LLM request failed (${res.status}): ${text.slice(0, 300)}`);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const data = await res.json();
|
|
49
|
+
const content = data.choices?.[0]?.message?.content ?? '';
|
|
50
|
+
const text = typeof content === 'string' ? content : JSON.stringify(content);
|
|
51
|
+
const rawUsage = data.usage;
|
|
52
|
+
const usage = {
|
|
53
|
+
prompt_tokens: rawUsage?.prompt_tokens ?? 0,
|
|
54
|
+
completion_tokens: rawUsage?.completion_tokens ?? 0,
|
|
55
|
+
total_tokens: rawUsage?.total_tokens ?? (rawUsage?.prompt_tokens ?? 0) + (rawUsage?.completion_tokens ?? 0),
|
|
56
|
+
};
|
|
57
|
+
return { content: text, usage };
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Create an LLM provider from config.
|
|
63
|
+
* @param {object} config - { baseURL?, apiKey?, model? }
|
|
64
|
+
* @returns {LLMProvider}
|
|
65
|
+
*/
|
|
66
|
+
function createProvider(config) {
|
|
67
|
+
if (!config || typeof config !== 'object') {
|
|
68
|
+
return new LLMProvider({});
|
|
69
|
+
}
|
|
70
|
+
return new LLMProvider(config);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export { LLMProvider, createProvider };
|
package/lib/llm/index.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared LLM module for te.js: generic client and parse utilities.
|
|
3
|
+
* Used by auto-docs, error-inference, and future LLM features.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export { LLMProvider, createProvider } from './client.js';
|
|
7
|
+
export { extractJSON, extractJSONArray, reconcileOrderedTags } from './parse.js';
|