aetherjs-router 1.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/README.md +541 -0
- package/index.js +21 -0
- package/package.json +41 -0
- package/src/aether-adapter.js +98 -0
- package/src/examples/basic-router.js +796 -0
- package/src/path-compiler.js +660 -0
- package/src/route-factory.js +326 -0
- package/src/router-factory.js +840 -0
- package/src/test/benchmark/router-benchmark.test.js +561 -0
|
@@ -0,0 +1,796 @@
|
|
|
1
|
+
// examples/basic-router.js - Comprehensive example demonstrating all router features
|
|
2
|
+
import { createAetherRouteFactory } from '../aether-adapter.js';
|
|
3
|
+
|
|
4
|
+
// Create router factory with enhanced features enabled
|
|
5
|
+
const routerFactory = createAetherRouteFactory({
|
|
6
|
+
prefix: '/api',
|
|
7
|
+
cacheSize: 2000,
|
|
8
|
+
parseQuery: true, // Enable query parameter pattern matching
|
|
9
|
+
autoParseQuery: true, // Automatically parse query parameters
|
|
10
|
+
enableVersioning: true // Enable versioning support
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
// ============================================
|
|
14
|
+
// 1. BASIC ROUTE DEFINITIONS
|
|
15
|
+
// ============================================
|
|
16
|
+
|
|
17
|
+
// Add global middleware for logging
|
|
18
|
+
routerFactory.use(async (ctx, next) => {
|
|
19
|
+
console.log(`[${new Date().toISOString()}] ${ctx.method} ${ctx.url}`);
|
|
20
|
+
const startTime = Date.now();
|
|
21
|
+
await next();
|
|
22
|
+
const duration = Date.now() - startTime;
|
|
23
|
+
console.log(`Request completed in ${duration}ms`);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
// Basic health check endpoint
|
|
27
|
+
routerFactory.get('/health', (ctx) => {
|
|
28
|
+
ctx.setStatus(200);
|
|
29
|
+
ctx.json({
|
|
30
|
+
status: 'ok',
|
|
31
|
+
timestamp: Date.now(),
|
|
32
|
+
version: '1.0.0'
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// ============================================
|
|
37
|
+
// 2. QUERY PARAMETER SUPPORT EXAMPLES
|
|
38
|
+
// ============================================
|
|
39
|
+
|
|
40
|
+
// Enable automatic query parameter parsing
|
|
41
|
+
routerFactory.use((ctx, next) => {
|
|
42
|
+
if (ctx.url && ctx.url.includes('?')) {
|
|
43
|
+
const [path, queryString] = ctx.url.split('?');
|
|
44
|
+
ctx.path = path;
|
|
45
|
+
ctx.query = Object.fromEntries(new URLSearchParams(queryString));
|
|
46
|
+
ctx.originalUrl = ctx.url;
|
|
47
|
+
} else {
|
|
48
|
+
ctx.query = {};
|
|
49
|
+
}
|
|
50
|
+
return next();
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// Route with query parameters in pattern
|
|
54
|
+
routerFactory.get('/search?q=:query&page=:page?&limit=:limit?', (ctx) => {
|
|
55
|
+
const { query, page = '1', limit = '10' } = ctx.params;
|
|
56
|
+
|
|
57
|
+
ctx.setStatus(200);
|
|
58
|
+
ctx.json({
|
|
59
|
+
query,
|
|
60
|
+
page: parseInt(page),
|
|
61
|
+
limit: parseInt(limit),
|
|
62
|
+
results: [
|
|
63
|
+
{ id: 1, title: `Result for: ${query}` },
|
|
64
|
+
{ id: 2, title: `Another result for: ${query}` }
|
|
65
|
+
],
|
|
66
|
+
total: 100,
|
|
67
|
+
currentPage: parseInt(page)
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// Route with mixed path and query parameters
|
|
72
|
+
routerFactory.get('/users/:id?fields=:fields?&expand=:expand?', (ctx) => {
|
|
73
|
+
const { id, fields = 'id,name,email', expand = '' } = ctx.params;
|
|
74
|
+
|
|
75
|
+
ctx.setStatus(200);
|
|
76
|
+
ctx.json({
|
|
77
|
+
user: {
|
|
78
|
+
id,
|
|
79
|
+
name: `User ${id}`,
|
|
80
|
+
email: `user${id}@example.com`,
|
|
81
|
+
fields: fields.split(','),
|
|
82
|
+
expand: expand.split(',').filter(Boolean)
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// Advanced query parameter example with validation
|
|
88
|
+
routerFactory.get('/products?category=:category&minPrice=:minPrice?&maxPrice=:maxPrice?&sort=:sort?', (ctx) => {
|
|
89
|
+
const { category, minPrice = '0', maxPrice = '1000000', sort = 'name' } = ctx.params;
|
|
90
|
+
|
|
91
|
+
// Validate parameters
|
|
92
|
+
const validSortFields = ['name', 'price', 'rating', 'date'];
|
|
93
|
+
if (!validSortFields.includes(sort)) {
|
|
94
|
+
ctx.setStatus(400);
|
|
95
|
+
ctx.json({ error: 'Invalid sort field' });
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
ctx.setStatus(200);
|
|
100
|
+
ctx.json({
|
|
101
|
+
category,
|
|
102
|
+
filters: {
|
|
103
|
+
minPrice: parseFloat(minPrice),
|
|
104
|
+
maxPrice: parseFloat(maxPrice),
|
|
105
|
+
sort
|
|
106
|
+
},
|
|
107
|
+
products: [
|
|
108
|
+
{ id: 1, name: 'Product A', price: 99.99, category },
|
|
109
|
+
{ id: 2, name: 'Product B', price: 149.99, category }
|
|
110
|
+
]
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
// ============================================
|
|
115
|
+
// 3. VERSIONING EXAMPLES
|
|
116
|
+
// ============================================
|
|
117
|
+
|
|
118
|
+
// Version 1 API routes
|
|
119
|
+
routerFactory.group('/v1', (v1) => {
|
|
120
|
+
// Add version-specific middleware
|
|
121
|
+
v1.use((ctx, next) => {
|
|
122
|
+
ctx.apiVersion = 'v1';
|
|
123
|
+
ctx.deprecated = false;
|
|
124
|
+
return next();
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
// Simple user endpoints for v1
|
|
128
|
+
v1.get('/users', (ctx) => {
|
|
129
|
+
ctx.setStatus(200);
|
|
130
|
+
ctx.json({
|
|
131
|
+
version: ctx.apiVersion,
|
|
132
|
+
users: [
|
|
133
|
+
{ id: 1, name: 'Alice', email: 'alice@example.com' },
|
|
134
|
+
{ id: 2, name: 'Bob', email: 'bob@example.com' }
|
|
135
|
+
]
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
v1.get('/users/:id', (ctx) => {
|
|
140
|
+
ctx.setStatus(200);
|
|
141
|
+
ctx.json({
|
|
142
|
+
version: ctx.apiVersion,
|
|
143
|
+
user: {
|
|
144
|
+
id: ctx.params.id,
|
|
145
|
+
name: `User ${ctx.params.id}`,
|
|
146
|
+
email: `user${ctx.params.id}@example.com`
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
// Products with query parameters in v1
|
|
152
|
+
v1.get('/products?category=:category&page=:page?', (ctx) => {
|
|
153
|
+
ctx.setStatus(200);
|
|
154
|
+
ctx.json({
|
|
155
|
+
version: ctx.apiVersion,
|
|
156
|
+
category: ctx.params.category,
|
|
157
|
+
page: ctx.params.page || '1',
|
|
158
|
+
products: []
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
// Version 2 API routes with enhanced features
|
|
164
|
+
routerFactory.group('/v2', (v2) => {
|
|
165
|
+
// Add version-specific middleware
|
|
166
|
+
v2.use((ctx, next) => {
|
|
167
|
+
ctx.apiVersion = 'v2';
|
|
168
|
+
ctx.features = ['enhanced-security', 'rate-limiting', 'caching'];
|
|
169
|
+
return next();
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
// Enhanced user endpoints for v2
|
|
173
|
+
v2.get('/users', (ctx) => {
|
|
174
|
+
const { page = '1', limit = '20', sort = 'name' } = ctx.query;
|
|
175
|
+
|
|
176
|
+
ctx.setStatus(200);
|
|
177
|
+
ctx.json({
|
|
178
|
+
version: ctx.apiVersion,
|
|
179
|
+
features: ctx.features,
|
|
180
|
+
pagination: {
|
|
181
|
+
page: parseInt(page),
|
|
182
|
+
limit: parseInt(limit),
|
|
183
|
+
sort
|
|
184
|
+
},
|
|
185
|
+
users: [
|
|
186
|
+
{
|
|
187
|
+
id: 1,
|
|
188
|
+
name: 'Alice',
|
|
189
|
+
email: 'alice@example.com',
|
|
190
|
+
metadata: { createdAt: '2024-01-01', updatedAt: '2024-01-15' }
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
id: 2,
|
|
194
|
+
name: 'Bob',
|
|
195
|
+
email: 'bob@example.com',
|
|
196
|
+
metadata: { createdAt: '2024-01-02', updatedAt: '2024-01-16' }
|
|
197
|
+
}
|
|
198
|
+
]
|
|
199
|
+
});
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
// Enhanced user detail with query parameters
|
|
203
|
+
v2.get('/users/:id?include=:include?&fields=:fields?', (ctx) => {
|
|
204
|
+
const { id, include = '', fields = '' } = ctx.params;
|
|
205
|
+
|
|
206
|
+
const userData = {
|
|
207
|
+
id,
|
|
208
|
+
name: `User ${id}`,
|
|
209
|
+
email: `user${id}@example.com`,
|
|
210
|
+
profile: {
|
|
211
|
+
bio: `Bio for user ${id}`,
|
|
212
|
+
avatar: `https://example.com/avatars/${id}.jpg`
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
// Handle include parameter
|
|
217
|
+
const includes = include.split(',').filter(Boolean);
|
|
218
|
+
if (includes.includes('posts')) {
|
|
219
|
+
userData.posts = [
|
|
220
|
+
{ id: 101, title: 'First Post', content: 'Content here' },
|
|
221
|
+
{ id: 102, title: 'Second Post', content: 'More content' }
|
|
222
|
+
];
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (includes.includes('comments')) {
|
|
226
|
+
userData.comments = [
|
|
227
|
+
{ id: 201, postId: 101, content: 'Great post!' }
|
|
228
|
+
];
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Handle fields parameter
|
|
232
|
+
const requestedFields = fields.split(',').filter(Boolean);
|
|
233
|
+
if (requestedFields.length > 0) {
|
|
234
|
+
const filteredData = {};
|
|
235
|
+
requestedFields.forEach(field => {
|
|
236
|
+
if (userData[field] !== undefined) {
|
|
237
|
+
filteredData[field] = userData[field];
|
|
238
|
+
}
|
|
239
|
+
});
|
|
240
|
+
ctx.setStatus(200);
|
|
241
|
+
ctx.json({
|
|
242
|
+
version: ctx.apiVersion,
|
|
243
|
+
user: filteredData
|
|
244
|
+
});
|
|
245
|
+
} else {
|
|
246
|
+
ctx.setStatus(200);
|
|
247
|
+
ctx.json({
|
|
248
|
+
version: ctx.apiVersion,
|
|
249
|
+
user: userData
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
// Products with advanced filtering in v2
|
|
255
|
+
v2.get('/products?category=:category&minPrice=:minPrice?&maxPrice=:maxPrice?&sort=:sort?&page=:page?', (ctx) => {
|
|
256
|
+
const { category, minPrice = '0', maxPrice = '1000000', sort = 'name', page = '1' } = ctx.params;
|
|
257
|
+
|
|
258
|
+
ctx.setStatus(200);
|
|
259
|
+
ctx.json({
|
|
260
|
+
version: ctx.apiVersion,
|
|
261
|
+
filters: {
|
|
262
|
+
category,
|
|
263
|
+
priceRange: {
|
|
264
|
+
min: parseFloat(minPrice),
|
|
265
|
+
max: parseFloat(maxPrice)
|
|
266
|
+
},
|
|
267
|
+
sort,
|
|
268
|
+
page: parseInt(page)
|
|
269
|
+
},
|
|
270
|
+
products: [
|
|
271
|
+
{
|
|
272
|
+
id: 1,
|
|
273
|
+
name: 'Premium Product A',
|
|
274
|
+
price: 199.99,
|
|
275
|
+
category,
|
|
276
|
+
rating: 4.5,
|
|
277
|
+
tags: ['premium', 'featured']
|
|
278
|
+
},
|
|
279
|
+
{
|
|
280
|
+
id: 2,
|
|
281
|
+
name: 'Standard Product B',
|
|
282
|
+
price: 99.99,
|
|
283
|
+
category,
|
|
284
|
+
rating: 4.0,
|
|
285
|
+
tags: ['standard']
|
|
286
|
+
}
|
|
287
|
+
],
|
|
288
|
+
metadata: {
|
|
289
|
+
total: 50,
|
|
290
|
+
page: parseInt(page),
|
|
291
|
+
pageSize: 20,
|
|
292
|
+
hasMore: parseInt(page) < 3
|
|
293
|
+
}
|
|
294
|
+
});
|
|
295
|
+
});
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
// ============================================
|
|
299
|
+
// 4. NESTED GROUPING EXAMPLES
|
|
300
|
+
// ============================================
|
|
301
|
+
|
|
302
|
+
// Admin routes with nested grouping
|
|
303
|
+
routerFactory.group('/admin', (admin) => {
|
|
304
|
+
// Admin authentication middleware
|
|
305
|
+
admin.use(async (ctx, next) => {
|
|
306
|
+
const token = ctx.headers['x-admin-token'];
|
|
307
|
+
if (!token || token !== 'admin-secret-token') {
|
|
308
|
+
ctx.setStatus(401);
|
|
309
|
+
ctx.json({
|
|
310
|
+
error: 'Admin access required',
|
|
311
|
+
message: 'Valid x-admin-token header is required'
|
|
312
|
+
});
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
ctx.isAdmin = true;
|
|
316
|
+
await next();
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
// Admin dashboard
|
|
320
|
+
admin.get('/dashboard?view=:view?', (ctx) => {
|
|
321
|
+
const view = ctx.params.view || 'overview';
|
|
322
|
+
|
|
323
|
+
ctx.setStatus(200);
|
|
324
|
+
ctx.json({
|
|
325
|
+
view,
|
|
326
|
+
stats: {
|
|
327
|
+
totalUsers: 1500,
|
|
328
|
+
activeUsers: 1250,
|
|
329
|
+
revenue: 50000,
|
|
330
|
+
growth: 15.5
|
|
331
|
+
},
|
|
332
|
+
recentActivity: [
|
|
333
|
+
{ id: 1, action: 'user.created', timestamp: '2024-01-15T10:30:00Z' },
|
|
334
|
+
{ id: 2, action: 'order.completed', timestamp: '2024-01-15T10:25:00Z' }
|
|
335
|
+
]
|
|
336
|
+
});
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
// Nested grouping for user management
|
|
340
|
+
admin.group('/users', (users) => {
|
|
341
|
+
// User list with query parameters
|
|
342
|
+
users.get('?status=:status?&role=:role?&page=:page?', (ctx) => {
|
|
343
|
+
const { status = 'active', role = 'all', page = '1' } = ctx.params;
|
|
344
|
+
|
|
345
|
+
ctx.setStatus(200);
|
|
346
|
+
ctx.json({
|
|
347
|
+
filters: { status, role, page: parseInt(page) },
|
|
348
|
+
users: [
|
|
349
|
+
{ id: 1, name: 'Admin User', email: 'admin@example.com', role: 'admin', status: 'active' },
|
|
350
|
+
{ id: 2, name: 'Moderator', email: 'mod@example.com', role: 'moderator', status: 'active' }
|
|
351
|
+
],
|
|
352
|
+
pagination: {
|
|
353
|
+
total: 100,
|
|
354
|
+
page: parseInt(page),
|
|
355
|
+
pageSize: 20
|
|
356
|
+
}
|
|
357
|
+
});
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
// User detail with query parameters
|
|
361
|
+
users.get('/:id?include=:include?', (ctx) => {
|
|
362
|
+
const { id, include = '' } = ctx.params;
|
|
363
|
+
const includes = include.split(',').filter(Boolean);
|
|
364
|
+
|
|
365
|
+
const userDetail = {
|
|
366
|
+
id,
|
|
367
|
+
name: `Admin User ${id}`,
|
|
368
|
+
email: `admin${id}@example.com`,
|
|
369
|
+
role: 'admin',
|
|
370
|
+
status: 'active',
|
|
371
|
+
createdAt: '2024-01-01T00:00:00Z',
|
|
372
|
+
lastLogin: '2024-01-15T10:00:00Z'
|
|
373
|
+
};
|
|
374
|
+
|
|
375
|
+
if (includes.includes('permissions')) {
|
|
376
|
+
userDetail.permissions = ['read', 'write', 'delete', 'manage'];
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
if (includes.includes('activity')) {
|
|
380
|
+
userDetail.recentActivity = [
|
|
381
|
+
{ action: 'login', timestamp: '2024-01-15T10:00:00Z' },
|
|
382
|
+
{ action: 'settings.updated', timestamp: '2024-01-14T15:30:00Z' }
|
|
383
|
+
];
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
ctx.setStatus(200);
|
|
387
|
+
ctx.json(userDetail);
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
// Create user
|
|
391
|
+
users.post('/', async (ctx) => {
|
|
392
|
+
const userData = ctx.body;
|
|
393
|
+
|
|
394
|
+
ctx.setStatus(201);
|
|
395
|
+
ctx.json({
|
|
396
|
+
message: 'User created successfully',
|
|
397
|
+
user: {
|
|
398
|
+
id: Date.now(),
|
|
399
|
+
...userData,
|
|
400
|
+
createdAt: new Date().toISOString(),
|
|
401
|
+
createdBy: 'admin'
|
|
402
|
+
}
|
|
403
|
+
});
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
// Update user
|
|
407
|
+
users.put('/:id', async (ctx) => {
|
|
408
|
+
const { id } = ctx.params;
|
|
409
|
+
const updateData = ctx.body;
|
|
410
|
+
|
|
411
|
+
ctx.setStatus(200);
|
|
412
|
+
ctx.json({
|
|
413
|
+
message: 'User updated successfully',
|
|
414
|
+
user: {
|
|
415
|
+
id,
|
|
416
|
+
...updateData,
|
|
417
|
+
updatedAt: new Date().toISOString(),
|
|
418
|
+
updatedBy: 'admin'
|
|
419
|
+
}
|
|
420
|
+
});
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
// Delete user
|
|
424
|
+
users.delete('/:id', (ctx) => {
|
|
425
|
+
const { id } = ctx.params;
|
|
426
|
+
|
|
427
|
+
ctx.setStatus(200);
|
|
428
|
+
ctx.json({
|
|
429
|
+
message: `User ${id} deleted successfully`,
|
|
430
|
+
deletedAt: new Date().toISOString()
|
|
431
|
+
});
|
|
432
|
+
});
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
// Nested grouping for product management
|
|
436
|
+
admin.group('/products', (products) => {
|
|
437
|
+
// Product list with advanced query parameters
|
|
438
|
+
products.get('?status=:status?&category=:category?&sort=:sort?&order=:order?', (ctx) => {
|
|
439
|
+
const {
|
|
440
|
+
status = 'active',
|
|
441
|
+
category = 'all',
|
|
442
|
+
sort = 'createdAt',
|
|
443
|
+
order = 'desc'
|
|
444
|
+
} = ctx.params;
|
|
445
|
+
|
|
446
|
+
ctx.setStatus(200);
|
|
447
|
+
ctx.json({
|
|
448
|
+
filters: { status, category, sort, order },
|
|
449
|
+
products: [
|
|
450
|
+
{
|
|
451
|
+
id: 1,
|
|
452
|
+
name: 'Premium Product',
|
|
453
|
+
category: 'electronics',
|
|
454
|
+
status: 'active',
|
|
455
|
+
price: 299.99,
|
|
456
|
+
stock: 50,
|
|
457
|
+
createdAt: '2024-01-10T00:00:00Z'
|
|
458
|
+
}
|
|
459
|
+
],
|
|
460
|
+
total: 1
|
|
461
|
+
});
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
// Create product
|
|
465
|
+
products.post('/', async (ctx) => {
|
|
466
|
+
const productData = ctx.body;
|
|
467
|
+
|
|
468
|
+
ctx.setStatus(201);
|
|
469
|
+
ctx.json({
|
|
470
|
+
message: 'Product created successfully',
|
|
471
|
+
product: {
|
|
472
|
+
id: Date.now(),
|
|
473
|
+
...productData,
|
|
474
|
+
createdAt: new Date().toISOString(),
|
|
475
|
+
createdBy: 'admin'
|
|
476
|
+
}
|
|
477
|
+
});
|
|
478
|
+
});
|
|
479
|
+
});
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
// ============================================
|
|
483
|
+
// 5. RESTFUL RESOURCE EXAMPLES
|
|
484
|
+
// ============================================
|
|
485
|
+
|
|
486
|
+
// Articles resource with query parameter support
|
|
487
|
+
routerFactory.group('/articles', (articles) => {
|
|
488
|
+
// GET /api/articles?page=:page&limit=:limit&category=:category?
|
|
489
|
+
articles.get('?page=:page?&limit=:limit?&category=:category?', (ctx) => {
|
|
490
|
+
const { page = '1', limit = '10', category = 'all' } = ctx.params;
|
|
491
|
+
|
|
492
|
+
ctx.setStatus(200);
|
|
493
|
+
ctx.json({
|
|
494
|
+
articles: [
|
|
495
|
+
{ id: 1, title: 'Article 1', category, author: 'Author 1' },
|
|
496
|
+
{ id: 2, title: 'Article 2', category, author: 'Author 2' }
|
|
497
|
+
],
|
|
498
|
+
pagination: {
|
|
499
|
+
page: parseInt(page),
|
|
500
|
+
limit: parseInt(limit),
|
|
501
|
+
total: 100,
|
|
502
|
+
totalPages: 10
|
|
503
|
+
},
|
|
504
|
+
filters: { category }
|
|
505
|
+
});
|
|
506
|
+
});
|
|
507
|
+
|
|
508
|
+
// POST /api/articles
|
|
509
|
+
articles.post('/', async (ctx) => {
|
|
510
|
+
const articleData = ctx.body;
|
|
511
|
+
|
|
512
|
+
ctx.setStatus(201);
|
|
513
|
+
ctx.json({
|
|
514
|
+
message: 'Article created successfully',
|
|
515
|
+
article: {
|
|
516
|
+
id: Date.now(),
|
|
517
|
+
...articleData,
|
|
518
|
+
createdAt: new Date().toISOString(),
|
|
519
|
+
slug: articleData.title.toLowerCase().replace(/[^a-z0-9]+/g, '-')
|
|
520
|
+
}
|
|
521
|
+
});
|
|
522
|
+
});
|
|
523
|
+
|
|
524
|
+
// GET /api/articles/:id?include=:include?
|
|
525
|
+
articles.get('/:id?include=:include?', (ctx) => {
|
|
526
|
+
const { id, include = '' } = ctx.params;
|
|
527
|
+
const includes = include.split(',').filter(Boolean);
|
|
528
|
+
|
|
529
|
+
const article = {
|
|
530
|
+
id,
|
|
531
|
+
title: `Article ${id}`,
|
|
532
|
+
content: `Content for article ${id}`,
|
|
533
|
+
author: 'John Doe',
|
|
534
|
+
publishedAt: '2024-01-15T10:00:00Z',
|
|
535
|
+
tags: ['technology', 'programming']
|
|
536
|
+
};
|
|
537
|
+
|
|
538
|
+
if (includes.includes('comments')) {
|
|
539
|
+
article.comments = [
|
|
540
|
+
{ id: 1, user: 'Alice', comment: 'Great article!' },
|
|
541
|
+
{ id: 2, user: 'Bob', comment: 'Very informative' }
|
|
542
|
+
];
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
if (includes.includes('stats')) {
|
|
546
|
+
article.stats = {
|
|
547
|
+
views: 1500,
|
|
548
|
+
likes: 120,
|
|
549
|
+
shares: 45
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
ctx.setStatus(200);
|
|
554
|
+
ctx.json(article);
|
|
555
|
+
});
|
|
556
|
+
|
|
557
|
+
// PUT /api/articles/:id
|
|
558
|
+
articles.put('/:id', async (ctx) => {
|
|
559
|
+
const { id } = ctx.params;
|
|
560
|
+
const updateData = ctx.body;
|
|
561
|
+
|
|
562
|
+
ctx.setStatus(200);
|
|
563
|
+
ctx.json({
|
|
564
|
+
message: 'Article updated successfully',
|
|
565
|
+
article: {
|
|
566
|
+
id,
|
|
567
|
+
...updateData,
|
|
568
|
+
updatedAt: new Date().toISOString()
|
|
569
|
+
}
|
|
570
|
+
});
|
|
571
|
+
});
|
|
572
|
+
|
|
573
|
+
// DELETE /api/articles/:id
|
|
574
|
+
articles.delete('/:id', (ctx) => {
|
|
575
|
+
const { id } = ctx.params;
|
|
576
|
+
|
|
577
|
+
ctx.setStatus(200);
|
|
578
|
+
ctx.json({
|
|
579
|
+
message: `Article ${id} deleted successfully`,
|
|
580
|
+
deletedAt: new Date().toISOString()
|
|
581
|
+
});
|
|
582
|
+
});
|
|
583
|
+
|
|
584
|
+
// Nested routes for article comments
|
|
585
|
+
articles.group('/:articleId/comments', (comments) => {
|
|
586
|
+
// GET /api/articles/:articleId/comments?page=:page?
|
|
587
|
+
comments.get('?page=:page?', (ctx) => {
|
|
588
|
+
const { articleId, page = '1' } = ctx.params;
|
|
589
|
+
|
|
590
|
+
ctx.setStatus(200);
|
|
591
|
+
ctx.json({
|
|
592
|
+
articleId,
|
|
593
|
+
comments: [
|
|
594
|
+
{ id: 1, text: 'Great article!', author: 'Alice' },
|
|
595
|
+
{ id: 2, text: 'Very helpful', author: 'Bob' }
|
|
596
|
+
],
|
|
597
|
+
page: parseInt(page)
|
|
598
|
+
});
|
|
599
|
+
});
|
|
600
|
+
|
|
601
|
+
// POST /api/articles/:articleId/comments
|
|
602
|
+
comments.post('/', async (ctx) => {
|
|
603
|
+
const { articleId } = ctx.params;
|
|
604
|
+
const commentData = ctx.body;
|
|
605
|
+
|
|
606
|
+
ctx.setStatus(201);
|
|
607
|
+
ctx.json({
|
|
608
|
+
message: 'Comment added successfully',
|
|
609
|
+
comment: {
|
|
610
|
+
id: Date.now(),
|
|
611
|
+
articleId,
|
|
612
|
+
...commentData,
|
|
613
|
+
createdAt: new Date().toISOString()
|
|
614
|
+
}
|
|
615
|
+
});
|
|
616
|
+
});
|
|
617
|
+
});
|
|
618
|
+
});
|
|
619
|
+
|
|
620
|
+
// ============================================
|
|
621
|
+
// 6. ERROR HANDLING AND MIDDLEWARE EXAMPLES
|
|
622
|
+
// ============================================
|
|
623
|
+
|
|
624
|
+
// Error handling middleware
|
|
625
|
+
routerFactory.use(async (ctx, next) => {
|
|
626
|
+
try {
|
|
627
|
+
await next();
|
|
628
|
+
} catch (error) {
|
|
629
|
+
console.error('Route error:', error);
|
|
630
|
+
ctx.setStatus(500);
|
|
631
|
+
ctx.json({
|
|
632
|
+
error: 'Internal Server Error',
|
|
633
|
+
message: error.message,
|
|
634
|
+
timestamp: new Date().toISOString()
|
|
635
|
+
});
|
|
636
|
+
}
|
|
637
|
+
});
|
|
638
|
+
|
|
639
|
+
// 404 handler
|
|
640
|
+
routerFactory.use((ctx) => {
|
|
641
|
+
ctx.setStatus(404);
|
|
642
|
+
ctx.json({
|
|
643
|
+
error: 'Not Found',
|
|
644
|
+
message: `Route ${ctx.method} ${ctx.url} not found`,
|
|
645
|
+
timestamp: new Date().toISOString(),
|
|
646
|
+
availableRoutes: routerFactory.getRoutes().map(r => `${r.method} ${r.path}`)
|
|
647
|
+
});
|
|
648
|
+
});
|
|
649
|
+
|
|
650
|
+
// ============================================
|
|
651
|
+
// 7. UTILITY FUNCTIONS AND TESTING
|
|
652
|
+
// ============================================
|
|
653
|
+
|
|
654
|
+
// Get router middleware for AetherJS pipeline
|
|
655
|
+
const routerMiddleware = routerFactory.middleware();
|
|
656
|
+
|
|
657
|
+
// Create a simple HTTP server for testing all features
|
|
658
|
+
import http from 'http';
|
|
659
|
+
|
|
660
|
+
const server = http.createServer(async (req, res) => {
|
|
661
|
+
const ctx = {
|
|
662
|
+
method: req.method,
|
|
663
|
+
url: req.url,
|
|
664
|
+
headers: req.headers,
|
|
665
|
+
body: null,
|
|
666
|
+
setStatus: (code) => { res.statusCode = code; },
|
|
667
|
+
setHeader: (key, value) => { res.setHeader(key, value); },
|
|
668
|
+
json: (data) => {
|
|
669
|
+
res.setHeader('Content-Type', 'application/json');
|
|
670
|
+
res.end(JSON.stringify(data));
|
|
671
|
+
}
|
|
672
|
+
};
|
|
673
|
+
|
|
674
|
+
// Parse request body for POST, PUT, PATCH requests
|
|
675
|
+
if (['POST', 'PUT', 'PATCH'].includes(req.method)) {
|
|
676
|
+
const body = [];
|
|
677
|
+
req.on('data', (chunk) => {
|
|
678
|
+
body.push(chunk);
|
|
679
|
+
});
|
|
680
|
+
|
|
681
|
+
await new Promise((resolve) => {
|
|
682
|
+
req.on('end', () => {
|
|
683
|
+
try {
|
|
684
|
+
ctx.body = body.length > 0 ? JSON.parse(Buffer.concat(body).toString()) : null;
|
|
685
|
+
} catch (error) {
|
|
686
|
+
ctx.body = null;
|
|
687
|
+
}
|
|
688
|
+
resolve();
|
|
689
|
+
});
|
|
690
|
+
});
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
try {
|
|
694
|
+
await routerMiddleware(ctx, () => {
|
|
695
|
+
// If no route matches, the 404 handler will catch it
|
|
696
|
+
});
|
|
697
|
+
} catch (error) {
|
|
698
|
+
console.error('Router error:', error);
|
|
699
|
+
ctx.setStatus(500);
|
|
700
|
+
ctx.json({
|
|
701
|
+
error: 'Internal Server Error',
|
|
702
|
+
message: error.message
|
|
703
|
+
});
|
|
704
|
+
}
|
|
705
|
+
});
|
|
706
|
+
|
|
707
|
+
// Start the server
|
|
708
|
+
const PORT = 3003;
|
|
709
|
+
server.listen(PORT, () => {
|
|
710
|
+
console.log(`๐ Enhanced router test server running on http://localhost:${PORT}`);
|
|
711
|
+
console.log('๐ Available routes:');
|
|
712
|
+
|
|
713
|
+
const routes = routerFactory.getRoutes();
|
|
714
|
+
routes.forEach(route => {
|
|
715
|
+
console.log(` ${route.method.padEnd(7)} ${route.path}`);
|
|
716
|
+
if (route.hasQueryParams) {
|
|
717
|
+
console.log(` Query params: ${route.queryParamNames.join(', ')}`);
|
|
718
|
+
}
|
|
719
|
+
if (route.pathParamNames.length > 0) {
|
|
720
|
+
console.log(` Path params: ${route.pathParamNames.join(', ')}`);
|
|
721
|
+
}
|
|
722
|
+
});
|
|
723
|
+
|
|
724
|
+
console.log('\n๐ฏ Test endpoints:');
|
|
725
|
+
console.log(' Basic:');
|
|
726
|
+
console.log(' GET /api/health');
|
|
727
|
+
console.log(' GET /api/v1/users');
|
|
728
|
+
console.log(' GET /api/v2/users?page=1&limit=20');
|
|
729
|
+
console.log('\n Query Parameters:');
|
|
730
|
+
console.log(' GET /api/search?q=test&page=2&limit=20');
|
|
731
|
+
console.log(' GET /api/users/123?fields=name,email&expand=posts');
|
|
732
|
+
console.log(' GET /api/products?category=electronics&minPrice=100&maxPrice=1000&sort=price');
|
|
733
|
+
console.log('\n Versioning:');
|
|
734
|
+
console.log(' GET /api/v1/users/123');
|
|
735
|
+
console.log(' GET /api/v2/users/123?include=posts,comments&fields=name,email');
|
|
736
|
+
console.log('\n Admin Routes:');
|
|
737
|
+
console.log(' GET /api/admin/dashboard?view=detailed');
|
|
738
|
+
console.log(' GET /api/admin/users?status=active&role=admin&page=1');
|
|
739
|
+
console.log('\n RESTful Resources:');
|
|
740
|
+
console.log(' GET /api/articles?page=1&limit=10&category=technology');
|
|
741
|
+
console.log(' GET /api/articles/456?include=comments,stats');
|
|
742
|
+
console.log(' GET /api/articles/789/comments?page=1');
|
|
743
|
+
|
|
744
|
+
console.log('\n๐ Router Statistics:');
|
|
745
|
+
const stats = routerFactory.getStats();
|
|
746
|
+
console.log(` Total routes: ${stats.totalRoutes}`);
|
|
747
|
+
console.log(` Routes with query params: ${stats.routesWithQueryParams}`);
|
|
748
|
+
console.log(` Cache size: ${stats.cacheSize}`);
|
|
749
|
+
console.log(` Cache hit rate: ${stats.cacheHitRate.toFixed(2)}%`);
|
|
750
|
+
console.log(` Available versions: ${stats.versions.join(', ')}`);
|
|
751
|
+
});
|
|
752
|
+
|
|
753
|
+
// Export router middleware for use in other modules
|
|
754
|
+
export default routerMiddleware;
|
|
755
|
+
|
|
756
|
+
// Export router factory for programmatic access
|
|
757
|
+
export { routerFactory };
|
|
758
|
+
|
|
759
|
+
// Example usage function
|
|
760
|
+
export function testRouterFeatures() {
|
|
761
|
+
console.log('\n๐งช Testing router features...');
|
|
762
|
+
|
|
763
|
+
// Test basic route matching
|
|
764
|
+
const testRoutes = [
|
|
765
|
+
{ method: 'GET', path: '/api/health' },
|
|
766
|
+
{ method: 'GET', path: '/api/v1/users' },
|
|
767
|
+
{ method: 'GET', path: '/api/v2/users?page=2&limit=30' },
|
|
768
|
+
{ method: 'GET', path: '/api/search?q=router&page=1' },
|
|
769
|
+
{ method: 'GET', path: '/api/admin/dashboard?view=stats' },
|
|
770
|
+
{ method: 'GET', path: '/api/articles/123?include=comments' }
|
|
771
|
+
];
|
|
772
|
+
|
|
773
|
+
testRoutes.forEach(test => {
|
|
774
|
+
const match = routerFactory.match(test.method, test.path);
|
|
775
|
+
if (match) {
|
|
776
|
+
console.log(`โ Route matched: ${test.method} ${test.path}`);
|
|
777
|
+
console.log(` Params: ${JSON.stringify(match.params)}`);
|
|
778
|
+
if (match.query) {
|
|
779
|
+
console.log(` Query: ${JSON.stringify(match.query)}`);
|
|
780
|
+
}
|
|
781
|
+
} else {
|
|
782
|
+
console.log(`โ No match for: ${test.method} ${test.path}`);
|
|
783
|
+
}
|
|
784
|
+
});
|
|
785
|
+
|
|
786
|
+
// Display all routes
|
|
787
|
+
console.log('\n๐ All registered routes:');
|
|
788
|
+
routerFactory.getRoutes().forEach((route, index) => {
|
|
789
|
+
console.log(`${index + 1}. ${route.method} ${route.path}`);
|
|
790
|
+
});
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
// Run tests if this file is executed directly
|
|
794
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
795
|
+
testRouterFeatures();
|
|
796
|
+
}
|