@ooneex/routing 0.0.2 → 0.1.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 +402 -333
- package/dist/index.d.ts +15 -14
- package/dist/index.js +56 -49
- package/dist/index.js.map +7 -6
- package/package.json +12 -12
- package/dist/ooneex-routing-0.0.1.tgz +0 -0
package/README.md
CHANGED
|
@@ -1,460 +1,529 @@
|
|
|
1
1
|
# @ooneex/routing
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A flexible HTTP routing system for TypeScript applications with decorator-based route definitions, parameter validation, permission handling, and type-safe route generation. This package provides a powerful foundation for building RESTful APIs with support for WebSocket routes.
|
|
4
|
+
|
|
5
|
+

|
|
6
|
+

|
|
7
|
+

|
|
8
|
+

|
|
9
|
+

|
|
10
|
+
|
|
11
|
+
## Features
|
|
12
|
+
|
|
13
|
+
✅ **Decorator-Based Routes** - Define routes using TypeScript decorators for clean, readable code
|
|
14
|
+
|
|
15
|
+
✅ **Parameter Validation** - Built-in validation for route params, query strings, and payloads
|
|
16
|
+
|
|
17
|
+
✅ **Type-Safe Route Names** - Enforce `namespace.resource.action` naming convention
|
|
18
|
+
|
|
19
|
+
✅ **WebSocket Support** - Define WebSocket routes alongside HTTP routes
|
|
20
|
+
|
|
21
|
+
✅ **Permission System** - Role-based access control with custom permission classes
|
|
22
|
+
|
|
23
|
+
✅ **Environment Filtering** - Restrict routes to specific environments
|
|
24
|
+
|
|
25
|
+
✅ **IP/Host Restrictions** - Limit access by IP address or hostname
|
|
26
|
+
|
|
27
|
+
✅ **Route Generation** - Type-safe URL generation with parameter interpolation
|
|
28
|
+
|
|
29
|
+
✅ **Container Integration** - Automatic controller registration with DI container
|
|
4
30
|
|
|
5
31
|
## Installation
|
|
6
32
|
|
|
33
|
+
### Bun
|
|
7
34
|
```bash
|
|
8
35
|
bun add @ooneex/routing
|
|
9
36
|
```
|
|
10
37
|
|
|
11
|
-
|
|
38
|
+
### pnpm
|
|
39
|
+
```bash
|
|
40
|
+
pnpm add @ooneex/routing
|
|
41
|
+
```
|
|
12
42
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
- 🛠️ Utilities for type string generation and path validation
|
|
18
|
-
- 🌐 Support for HTTP methods and WebSocket routes
|
|
19
|
-
- 📝 Built-in support for params, queries, payload, and response validation
|
|
43
|
+
### Yarn
|
|
44
|
+
```bash
|
|
45
|
+
yarn add @ooneex/routing
|
|
46
|
+
```
|
|
20
47
|
|
|
21
|
-
|
|
48
|
+
### npm
|
|
49
|
+
```bash
|
|
50
|
+
npm install @ooneex/routing
|
|
51
|
+
```
|
|
22
52
|
|
|
23
|
-
|
|
24
|
-
import { Route, type ContextType } from "@ooneex/routing";
|
|
25
|
-
import { Assert } from "@ooneex/validation";
|
|
53
|
+
## Usage
|
|
26
54
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
55
|
+
### Basic HTTP Route
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
import { Route } from '@ooneex/routing';
|
|
59
|
+
import type { IController, ContextType } from '@ooneex/controller';
|
|
60
|
+
import type { IResponse } from '@ooneex/http-response';
|
|
61
|
+
|
|
62
|
+
@Route.http({
|
|
63
|
+
name: 'api.users.list',
|
|
64
|
+
path: '/api/users',
|
|
65
|
+
method: 'GET',
|
|
66
|
+
description: 'List all users'
|
|
38
67
|
})
|
|
39
|
-
|
|
40
|
-
public async index(context: ContextType) {
|
|
41
|
-
const { id } = context.params;
|
|
42
|
-
|
|
43
|
-
// Your logic here
|
|
68
|
+
class UserListController implements IController {
|
|
69
|
+
public async index(context: ContextType): Promise<IResponse> {
|
|
44
70
|
return context.response.json({
|
|
45
|
-
id,
|
|
46
|
-
name: "John Doe",
|
|
47
|
-
email: "john@example.com",
|
|
71
|
+
users: [{ id: 1, name: 'John' }]
|
|
48
72
|
});
|
|
49
73
|
}
|
|
50
74
|
}
|
|
51
75
|
```
|
|
52
76
|
|
|
53
|
-
|
|
77
|
+
### Route with Parameters
|
|
54
78
|
|
|
55
|
-
|
|
79
|
+
```typescript
|
|
80
|
+
import { Route } from '@ooneex/routing';
|
|
81
|
+
import { type } from 'arktype';
|
|
82
|
+
|
|
83
|
+
@Route.http({
|
|
84
|
+
name: 'api.users.show',
|
|
85
|
+
path: '/api/users/:id',
|
|
86
|
+
method: 'GET',
|
|
87
|
+
description: 'Get user by ID',
|
|
88
|
+
params: {
|
|
89
|
+
id: type('string.uuid')
|
|
90
|
+
}
|
|
91
|
+
})
|
|
92
|
+
class UserShowController implements IController {
|
|
93
|
+
public async index(context: ContextType): Promise<IResponse> {
|
|
94
|
+
const { id } = context.params;
|
|
95
|
+
const user = await this.userRepository.findById(id);
|
|
96
|
+
|
|
97
|
+
return context.response.json({ user });
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
```
|
|
56
101
|
|
|
57
|
-
|
|
102
|
+
### Route with Query Validation
|
|
58
103
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
104
|
+
```typescript
|
|
105
|
+
import { Route } from '@ooneex/routing';
|
|
106
|
+
import { type } from 'arktype';
|
|
107
|
+
|
|
108
|
+
@Route.http({
|
|
109
|
+
name: 'api.products.search',
|
|
110
|
+
path: '/api/products',
|
|
111
|
+
method: 'GET',
|
|
112
|
+
description: 'Search products',
|
|
113
|
+
queries: type({
|
|
114
|
+
q: 'string',
|
|
115
|
+
page: 'number = 1',
|
|
116
|
+
limit: 'number = 10',
|
|
117
|
+
'category?': 'string'
|
|
118
|
+
})
|
|
119
|
+
})
|
|
120
|
+
class ProductSearchController implements IController {
|
|
121
|
+
public async index(context: ContextType): Promise<IResponse> {
|
|
122
|
+
const { q, page, limit, category } = context.queries;
|
|
123
|
+
|
|
124
|
+
const products = await this.search(q, { page, limit, category });
|
|
125
|
+
|
|
126
|
+
return context.response.json({ products });
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
```
|
|
67
130
|
|
|
68
|
-
### Route
|
|
131
|
+
### Route with Payload Validation
|
|
69
132
|
|
|
70
133
|
```typescript
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
134
|
+
import { Route } from '@ooneex/routing';
|
|
135
|
+
import { type } from 'arktype';
|
|
136
|
+
|
|
137
|
+
@Route.http({
|
|
138
|
+
name: 'api.users.create',
|
|
139
|
+
path: '/api/users',
|
|
140
|
+
method: 'POST',
|
|
141
|
+
description: 'Create a new user',
|
|
142
|
+
payload: type({
|
|
143
|
+
email: 'string.email',
|
|
144
|
+
name: 'string >= 2',
|
|
145
|
+
password: 'string >= 8'
|
|
146
|
+
})
|
|
147
|
+
})
|
|
148
|
+
class UserCreateController implements IController {
|
|
149
|
+
public async index(context: ContextType): Promise<IResponse> {
|
|
150
|
+
const { email, name, password } = context.payload;
|
|
151
|
+
|
|
152
|
+
const user = await this.userService.create({ email, name, password });
|
|
153
|
+
|
|
154
|
+
return context.response.json({ user }, 201);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
83
157
|
```
|
|
84
158
|
|
|
85
|
-
###
|
|
86
|
-
|
|
87
|
-
Routes support dynamic path parameters that are automatically extracted and validated:
|
|
159
|
+
### WebSocket Route
|
|
88
160
|
|
|
89
161
|
```typescript
|
|
90
|
-
@
|
|
91
|
-
|
|
92
|
-
|
|
162
|
+
import { Route } from '@ooneex/routing';
|
|
163
|
+
import type { IController, ContextType } from '@ooneex/socket';
|
|
164
|
+
|
|
165
|
+
@Route.socket({
|
|
166
|
+
name: 'api.chat.connect',
|
|
167
|
+
path: '/ws/chat/:roomId',
|
|
168
|
+
description: 'Connect to chat room',
|
|
93
169
|
params: {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
},
|
|
97
|
-
response: Assert({
|
|
98
|
-
success: "boolean",
|
|
99
|
-
message: "string",
|
|
100
|
-
}),
|
|
170
|
+
roomId: type('string')
|
|
171
|
+
}
|
|
101
172
|
})
|
|
102
|
-
|
|
103
|
-
public async index(context: ContextType) {
|
|
104
|
-
const {
|
|
105
|
-
|
|
173
|
+
class ChatController implements IController {
|
|
174
|
+
public async index(context: ContextType): Promise<IResponse> {
|
|
175
|
+
const { roomId } = context.params;
|
|
176
|
+
|
|
177
|
+
await context.channel.subscribe();
|
|
178
|
+
|
|
179
|
+
return context.response.json({
|
|
180
|
+
connected: true,
|
|
181
|
+
room: roomId
|
|
182
|
+
});
|
|
106
183
|
}
|
|
107
184
|
}
|
|
108
185
|
```
|
|
109
186
|
|
|
110
|
-
|
|
187
|
+
### Route with Role-Based Access
|
|
111
188
|
|
|
112
|
-
|
|
189
|
+
```typescript
|
|
190
|
+
import { Route } from '@ooneex/routing';
|
|
191
|
+
import { ERole } from '@ooneex/role';
|
|
192
|
+
|
|
193
|
+
@Route.http({
|
|
194
|
+
name: 'admin.users.delete',
|
|
195
|
+
path: '/admin/users/:id',
|
|
196
|
+
method: 'DELETE',
|
|
197
|
+
description: 'Delete a user (admin only)',
|
|
198
|
+
roles: [ERole.ADMIN, ERole.SUPER_ADMIN]
|
|
199
|
+
})
|
|
200
|
+
class UserDeleteController implements IController {
|
|
201
|
+
public async index(context: ContextType): Promise<IResponse> {
|
|
202
|
+
await this.userService.delete(context.params.id);
|
|
203
|
+
|
|
204
|
+
return context.response.json({ deleted: true });
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
```
|
|
113
208
|
|
|
114
|
-
|
|
209
|
+
### Generating URLs
|
|
115
210
|
|
|
116
|
-
|
|
211
|
+
```typescript
|
|
212
|
+
import { router } from '@ooneex/routing';
|
|
117
213
|
|
|
118
|
-
|
|
214
|
+
// Generate URL for a named route
|
|
215
|
+
const userUrl = router.generate('api.users.show', { id: '123' });
|
|
216
|
+
console.log(userUrl); // "/api/users/123"
|
|
119
217
|
|
|
120
|
-
|
|
218
|
+
// Generate URL with multiple parameters
|
|
219
|
+
const orderUrl = router.generate('api.users.orders.show', {
|
|
220
|
+
userId: '123',
|
|
221
|
+
orderId: '456'
|
|
222
|
+
});
|
|
223
|
+
console.log(orderUrl); // "/api/users/123/orders/456"
|
|
224
|
+
```
|
|
121
225
|
|
|
122
|
-
|
|
226
|
+
## API Reference
|
|
123
227
|
|
|
124
|
-
|
|
228
|
+
### Decorators
|
|
125
229
|
|
|
126
|
-
|
|
127
|
-
import { routeConfigToTypeString } from "@ooneex/routing";
|
|
128
|
-
import { type } from "arktype";
|
|
230
|
+
#### `@Route.http(config: RouteConfigType)`
|
|
129
231
|
|
|
130
|
-
|
|
131
|
-
params: {
|
|
132
|
-
id: type("string"),
|
|
133
|
-
emailId: type("string"),
|
|
134
|
-
},
|
|
135
|
-
payload: type({
|
|
136
|
-
name: "string",
|
|
137
|
-
email: "string",
|
|
138
|
-
}),
|
|
139
|
-
queries: type({
|
|
140
|
-
limit: "number",
|
|
141
|
-
offset: "number",
|
|
142
|
-
}),
|
|
143
|
-
response: type({
|
|
144
|
-
success: "boolean",
|
|
145
|
-
message: "string",
|
|
146
|
-
data: {
|
|
147
|
-
id: "string",
|
|
148
|
-
name: "string",
|
|
149
|
-
},
|
|
150
|
-
}),
|
|
151
|
-
};
|
|
232
|
+
Decorator for defining HTTP routes.
|
|
152
233
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
234
|
+
**Parameters:**
|
|
235
|
+
- `config.name` - Route name in `namespace.resource.action` format
|
|
236
|
+
- `config.path` - URL path with optional parameters (e.g., `/users/:id`)
|
|
237
|
+
- `config.method` - HTTP method (GET, POST, PUT, PATCH, DELETE, etc.)
|
|
238
|
+
- `config.description` - Human-readable description
|
|
239
|
+
- `config.params` - Parameter validation schema (optional)
|
|
240
|
+
- `config.queries` - Query string validation schema (optional)
|
|
241
|
+
- `config.payload` - Request body validation schema (optional)
|
|
242
|
+
- `config.response` - Response validation schema (optional)
|
|
243
|
+
- `config.roles` - Required roles for access (optional)
|
|
244
|
+
- `config.permission` - Custom permission class (optional)
|
|
245
|
+
- `config.env` - Allowed environments (optional)
|
|
246
|
+
- `config.ip` - Allowed IP addresses (optional)
|
|
247
|
+
- `config.host` - Allowed hostnames (optional)
|
|
248
|
+
- `config.cache` - Enable caching (optional)
|
|
249
|
+
- `config.generate` - Code generation options (optional)
|
|
156
250
|
|
|
157
|
-
**
|
|
251
|
+
**Example:**
|
|
158
252
|
```typescript
|
|
159
|
-
{
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
}
|
|
253
|
+
@Route.http({
|
|
254
|
+
name: 'api.products.update',
|
|
255
|
+
path: '/api/products/:id',
|
|
256
|
+
method: 'PUT',
|
|
257
|
+
description: 'Update a product',
|
|
258
|
+
params: { id: type('string.uuid') },
|
|
259
|
+
payload: type({ name: 'string', price: 'number' }),
|
|
260
|
+
roles: [ERole.ADMIN]
|
|
261
|
+
})
|
|
165
262
|
```
|
|
166
263
|
|
|
167
|
-
####
|
|
264
|
+
#### `@Route.socket(config: RouteConfigType)`
|
|
168
265
|
|
|
169
|
-
|
|
266
|
+
Decorator for defining WebSocket routes.
|
|
170
267
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
const typeDefinition = `export type ${typeName} = ${routeConfigToTypeString(config)}`;
|
|
174
|
-
|
|
175
|
-
// Generates:
|
|
176
|
-
// export type DeleteUserRouteConfig = {
|
|
177
|
-
// response: { success: boolean; message: string };
|
|
178
|
-
// params: { id: string; emailId: string };
|
|
179
|
-
// payload: { name: string };
|
|
180
|
-
// queries: { limit: number };
|
|
181
|
-
// }
|
|
182
|
-
```
|
|
268
|
+
**Parameters:**
|
|
269
|
+
Same as `@Route.http`, but `method` is ignored and `isSocket` is set to `true`.
|
|
183
270
|
|
|
184
|
-
|
|
271
|
+
---
|
|
185
272
|
|
|
186
|
-
|
|
187
|
-
// Generate typed API client methods
|
|
188
|
-
const routeConfig = {
|
|
189
|
-
params: { userId: type("string") },
|
|
190
|
-
response: type({ id: "string", name: "string" }),
|
|
191
|
-
};
|
|
273
|
+
### Classes
|
|
192
274
|
|
|
193
|
-
|
|
194
|
-
public async getUser(userId: string): Promise<{ id: string; name: string }> {
|
|
195
|
-
return this.fetch("/users/" + userId);
|
|
196
|
-
}
|
|
197
|
-
`;
|
|
198
|
-
```
|
|
275
|
+
#### `Router`
|
|
199
276
|
|
|
200
|
-
|
|
277
|
+
Main router class for managing routes.
|
|
201
278
|
|
|
202
|
-
|
|
203
|
-
// Generate API documentation with type information
|
|
204
|
-
const docString = `
|
|
205
|
-
## GET /users/:id
|
|
206
|
-
|
|
207
|
-
**Type Definition:**
|
|
208
|
-
\`\`\`typescript
|
|
209
|
-
${routeConfigToTypeString(userRouteConfig)}
|
|
210
|
-
\`\`\`
|
|
211
|
-
`;
|
|
212
|
-
```
|
|
279
|
+
**Methods:**
|
|
213
280
|
|
|
214
|
-
|
|
281
|
+
##### `addRoute(route: RouteConfigType): this`
|
|
215
282
|
|
|
216
|
-
|
|
283
|
+
Manually add a route to the router.
|
|
217
284
|
|
|
218
|
-
|
|
219
|
-
|
|
285
|
+
**Parameters:**
|
|
286
|
+
- `route` - The route configuration
|
|
220
287
|
|
|
221
|
-
|
|
222
|
-
isValidRoutePath("/users/:id/posts/:postId"); // true
|
|
223
|
-
isValidRoutePath("users"); // false (must start with /)
|
|
224
|
-
isValidRoutePath("/users//posts"); // false (no double slashes)
|
|
225
|
-
isValidRoutePath("/users/:"); // false (parameter needs name)
|
|
226
|
-
```
|
|
288
|
+
**Returns:** The router instance for chaining
|
|
227
289
|
|
|
228
|
-
|
|
290
|
+
**Throws:** `RouterException` if route name or path/method already exists
|
|
229
291
|
|
|
230
|
-
|
|
292
|
+
##### `findRouteByPath(path: string): RouteConfigType[] | null`
|
|
231
293
|
|
|
232
|
-
|
|
233
|
-
import { extractParameterNames } from "@ooneex/routing";
|
|
294
|
+
Find all routes registered for a specific path.
|
|
234
295
|
|
|
235
|
-
|
|
236
|
-
|
|
296
|
+
**Parameters:**
|
|
297
|
+
- `path` - The URL path
|
|
237
298
|
|
|
238
|
-
|
|
239
|
-
// ["userId", "postId"]
|
|
299
|
+
**Returns:** Array of route configurations or null
|
|
240
300
|
|
|
241
|
-
|
|
242
|
-
// []
|
|
243
|
-
```
|
|
301
|
+
##### `findRouteByName(name: RouteNameType): RouteConfigType | null`
|
|
244
302
|
|
|
245
|
-
|
|
303
|
+
Find a route by its unique name.
|
|
246
304
|
|
|
247
|
-
|
|
305
|
+
**Parameters:**
|
|
306
|
+
- `name` - The route name
|
|
248
307
|
|
|
249
|
-
|
|
308
|
+
**Returns:** Route configuration or null
|
|
250
309
|
|
|
251
|
-
|
|
252
|
-
import type { RoutePathType, ExtractParameters } from "@ooneex/routing";
|
|
310
|
+
##### `getRoutes(): Map<string, RouteConfigType[]>`
|
|
253
311
|
|
|
254
|
-
|
|
255
|
-
type ValidPath = RoutePathType<"/users/:id">;
|
|
256
|
-
type MultiParam = RoutePathType<"/users/:userId/posts/:postId">;
|
|
312
|
+
Get all registered routes.
|
|
257
313
|
|
|
258
|
-
|
|
259
|
-
type UserParams = ExtractParameters<"/users/:id">;
|
|
260
|
-
// Result: "id"
|
|
314
|
+
**Returns:** Map of path to route configurations
|
|
261
315
|
|
|
262
|
-
|
|
263
|
-
// Result: "userId" | "postId"
|
|
264
|
-
```
|
|
316
|
+
##### `getHttpRoutes(): Map<string, RouteConfigType[]>`
|
|
265
317
|
|
|
266
|
-
|
|
318
|
+
Get only HTTP routes (excludes WebSocket routes).
|
|
267
319
|
|
|
268
|
-
|
|
320
|
+
**Returns:** Map of path to HTTP route configurations
|
|
269
321
|
|
|
270
|
-
|
|
271
|
-
import type { RouteNameType } from "@ooneex/routing";
|
|
322
|
+
##### `getSocketRoutes(): Map<string, RouteConfigType>`
|
|
272
323
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
324
|
+
Get only WebSocket routes.
|
|
325
|
+
|
|
326
|
+
**Returns:** Map of path to WebSocket route configuration
|
|
327
|
+
|
|
328
|
+
##### `generate<P>(name: RouteNameType, params?: P): string`
|
|
329
|
+
|
|
330
|
+
Generate a URL for a named route.
|
|
331
|
+
|
|
332
|
+
**Parameters:**
|
|
333
|
+
- `name` - The route name
|
|
334
|
+
- `params` - Parameter values to interpolate
|
|
279
335
|
|
|
280
|
-
|
|
336
|
+
**Returns:** The generated URL path
|
|
281
337
|
|
|
282
|
-
|
|
338
|
+
**Throws:** `RouterException` if route not found or missing required parameters
|
|
283
339
|
|
|
340
|
+
**Example:**
|
|
284
341
|
```typescript
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
router.addRoute({
|
|
288
|
-
name: "api.users.list",
|
|
289
|
-
path: "/users",
|
|
290
|
-
method: "GET",
|
|
291
|
-
controller: UserListController,
|
|
292
|
-
description: "List all users",
|
|
293
|
-
isSocket: false,
|
|
294
|
-
});
|
|
342
|
+
const url = router.generate('api.users.show', { id: '123' });
|
|
295
343
|
```
|
|
296
344
|
|
|
297
|
-
###
|
|
345
|
+
### Types
|
|
346
|
+
|
|
347
|
+
#### `RouteNameType`
|
|
348
|
+
|
|
349
|
+
Route name following the `namespace.resource.action` pattern.
|
|
298
350
|
|
|
299
351
|
```typescript
|
|
300
|
-
|
|
301
|
-
|
|
352
|
+
type RouteNameType = `${RouteNamespace}.${string}.${RouteAction}`;
|
|
353
|
+
// Example: "api.users.list", "admin.products.create"
|
|
354
|
+
```
|
|
302
355
|
|
|
303
|
-
|
|
304
|
-
|
|
356
|
+
**Valid Namespaces:**
|
|
357
|
+
- `api`, `client`, `admin`, `public`, `auth`, `webhook`, `internal`, `external`, `system`, `health`, `metrics`, `docs`
|
|
305
358
|
|
|
306
|
-
|
|
307
|
-
|
|
359
|
+
**Valid Actions:**
|
|
360
|
+
- `list`, `show`, `create`, `update`, `delete`, `search`, `export`, `import`, and 200+ more
|
|
308
361
|
|
|
309
|
-
|
|
310
|
-
const httpRoutes = router.getHttpRoutes();
|
|
362
|
+
#### `RouteConfigType`
|
|
311
363
|
|
|
312
|
-
|
|
313
|
-
|
|
364
|
+
```typescript
|
|
365
|
+
type RouteConfigType = {
|
|
366
|
+
name: RouteNameType;
|
|
367
|
+
path: `/${string}`;
|
|
368
|
+
method: HttpMethodType;
|
|
369
|
+
params?: Record<string, AssertType | IAssert>;
|
|
370
|
+
queries?: AssertType | IAssert;
|
|
371
|
+
payload?: AssertType | IAssert;
|
|
372
|
+
response?: AssertType | IAssert;
|
|
373
|
+
controller: ControllerClassType;
|
|
374
|
+
description: string;
|
|
375
|
+
env?: Environment[];
|
|
376
|
+
ip?: string[];
|
|
377
|
+
host?: string[];
|
|
378
|
+
roles?: ERole[];
|
|
379
|
+
permission?: PermissionClassType;
|
|
380
|
+
cache?: boolean;
|
|
381
|
+
isSocket: boolean;
|
|
382
|
+
generate?: {
|
|
383
|
+
doc?: boolean;
|
|
384
|
+
fetcher?: boolean;
|
|
385
|
+
queryHook?: boolean;
|
|
386
|
+
};
|
|
387
|
+
};
|
|
314
388
|
```
|
|
315
389
|
|
|
316
|
-
|
|
390
|
+
#### `ExtractParameters<T>`
|
|
391
|
+
|
|
392
|
+
Utility type to extract parameter names from a route path.
|
|
317
393
|
|
|
318
394
|
```typescript
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
395
|
+
type Params = ExtractParameters<'/users/:id/orders/:orderId'>;
|
|
396
|
+
// Result: "id" | "orderId"
|
|
397
|
+
```
|
|
322
398
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
399
|
+
#### `RouteParameters<T>`
|
|
400
|
+
|
|
401
|
+
Utility type to create a typed record from route parameters.
|
|
402
|
+
|
|
403
|
+
```typescript
|
|
404
|
+
type Params = RouteParameters<'/users/:id'>;
|
|
405
|
+
// Result: { id: string }
|
|
328
406
|
```
|
|
329
407
|
|
|
330
|
-
## Advanced
|
|
408
|
+
## Advanced Usage
|
|
331
409
|
|
|
332
|
-
###
|
|
410
|
+
### Environment-Specific Routes
|
|
333
411
|
|
|
334
412
|
```typescript
|
|
335
|
-
import { Route } from
|
|
336
|
-
import {
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
description:
|
|
343
|
-
|
|
344
|
-
userId: Assert("string"),
|
|
345
|
-
},
|
|
346
|
-
payload: Assert({
|
|
347
|
-
permissions: "string[]",
|
|
348
|
-
expiresAt: "string.date.parse",
|
|
349
|
-
}),
|
|
350
|
-
queries: Assert({
|
|
351
|
-
"notify?": "boolean",
|
|
352
|
-
}),
|
|
353
|
-
response: Assert({
|
|
354
|
-
success: "boolean",
|
|
355
|
-
message: "string",
|
|
356
|
-
permissions: "string[]",
|
|
357
|
-
}),
|
|
358
|
-
env: [Environment.PRODUCTION, Environment.STAGING],
|
|
359
|
-
roles: [ERole.ADMIN, ERole.SUPER_ADMIN],
|
|
413
|
+
import { Route } from '@ooneex/routing';
|
|
414
|
+
import { Environment } from '@ooneex/app-env';
|
|
415
|
+
|
|
416
|
+
@Route.http({
|
|
417
|
+
name: 'api.debug.info',
|
|
418
|
+
path: '/api/debug',
|
|
419
|
+
method: 'GET',
|
|
420
|
+
description: 'Debug information (dev only)',
|
|
421
|
+
env: [Environment.LOCAL, Environment.DEVELOPMENT]
|
|
360
422
|
})
|
|
361
|
-
|
|
362
|
-
public async index(context: ContextType) {
|
|
363
|
-
|
|
423
|
+
class DebugController implements IController {
|
|
424
|
+
public async index(context: ContextType): Promise<IResponse> {
|
|
425
|
+
return context.response.json({
|
|
426
|
+
environment: context.app.env.env,
|
|
427
|
+
timestamp: new Date().toISOString()
|
|
428
|
+
});
|
|
364
429
|
}
|
|
365
430
|
}
|
|
366
431
|
```
|
|
367
432
|
|
|
368
|
-
###
|
|
433
|
+
### Custom Permission Classes
|
|
369
434
|
|
|
370
435
|
```typescript
|
|
371
|
-
@
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
}),
|
|
380
|
-
})
|
|
381
|
-
export class ChatRoomSocketController {
|
|
382
|
-
public async index(context: ContextType) {
|
|
383
|
-
// WebSocket handling
|
|
436
|
+
import { Route } from '@ooneex/routing';
|
|
437
|
+
import { Permission } from '@ooneex/permission';
|
|
438
|
+
import type { IUser } from '@ooneex/user';
|
|
439
|
+
|
|
440
|
+
class CanEditOwnProfile extends Permission {
|
|
441
|
+
public can(user: IUser | null, context: ContextType): boolean {
|
|
442
|
+
if (!user) return false;
|
|
443
|
+
return user.id === context.params.id;
|
|
384
444
|
}
|
|
385
445
|
}
|
|
446
|
+
|
|
447
|
+
@Route.http({
|
|
448
|
+
name: 'api.users.update',
|
|
449
|
+
path: '/api/users/:id',
|
|
450
|
+
method: 'PUT',
|
|
451
|
+
description: 'Update user profile',
|
|
452
|
+
permission: CanEditOwnProfile
|
|
453
|
+
})
|
|
454
|
+
class UserUpdateController implements IController {
|
|
455
|
+
// Only the user themselves can update their profile
|
|
456
|
+
}
|
|
386
457
|
```
|
|
387
458
|
|
|
388
|
-
###
|
|
459
|
+
### IP Address Restrictions
|
|
389
460
|
|
|
390
461
|
```typescript
|
|
391
|
-
@Route.
|
|
392
|
-
name:
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
payload: Assert({
|
|
398
|
-
"name?": "string",
|
|
399
|
-
"email?": "string.email",
|
|
400
|
-
"age?": "number >= 18",
|
|
401
|
-
"bio?": "string",
|
|
402
|
-
}),
|
|
403
|
-
response: Assert({
|
|
404
|
-
success: "boolean",
|
|
405
|
-
updated: "boolean",
|
|
406
|
-
}),
|
|
462
|
+
@Route.http({
|
|
463
|
+
name: 'internal.health.check',
|
|
464
|
+
path: '/internal/health',
|
|
465
|
+
method: 'GET',
|
|
466
|
+
description: 'Internal health check',
|
|
467
|
+
ip: ['127.0.0.1', '10.0.0.0/8', '192.168.1.0/24']
|
|
407
468
|
})
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
// Update logic
|
|
411
|
-
}
|
|
469
|
+
class HealthCheckController implements IController {
|
|
470
|
+
// Only accessible from specified IP ranges
|
|
412
471
|
}
|
|
413
472
|
```
|
|
414
473
|
|
|
415
|
-
|
|
474
|
+
### Code Generation Options
|
|
416
475
|
|
|
417
476
|
```typescript
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
expect(result).toContain("params:");
|
|
432
|
-
expect(result).toContain("id: string");
|
|
433
|
-
expect(result).toContain("response:");
|
|
434
|
-
expect(result).toContain("success: boolean");
|
|
435
|
-
});
|
|
436
|
-
|
|
437
|
-
test("extracts parameter names", () => {
|
|
438
|
-
const params = extractParameterNames("/users/:id/posts/:postId");
|
|
439
|
-
expect(params).toEqual(["id", "postId"]);
|
|
440
|
-
});
|
|
441
|
-
});
|
|
477
|
+
@Route.http({
|
|
478
|
+
name: 'api.users.list',
|
|
479
|
+
path: '/api/users',
|
|
480
|
+
method: 'GET',
|
|
481
|
+
description: 'List all users',
|
|
482
|
+
response: type({ users: 'User[]' }),
|
|
483
|
+
generate: {
|
|
484
|
+
doc: true, // Generate API documentation
|
|
485
|
+
fetcher: true, // Generate fetch client function
|
|
486
|
+
queryHook: true // Generate React Query hook
|
|
487
|
+
}
|
|
488
|
+
})
|
|
442
489
|
```
|
|
443
490
|
|
|
444
|
-
|
|
491
|
+
### Error Handling
|
|
445
492
|
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
493
|
+
```typescript
|
|
494
|
+
import { router, RouterException } from '@ooneex/routing';
|
|
495
|
+
|
|
496
|
+
try {
|
|
497
|
+
const url = router.generate('api.nonexistent.route', { id: '123' });
|
|
498
|
+
} catch (error) {
|
|
499
|
+
if (error instanceof RouterException) {
|
|
500
|
+
console.error('Route error:', error.message);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
## License
|
|
506
|
+
|
|
507
|
+
This project is licensed under the MIT License - see the [LICENSE](./LICENSE) file for details.
|
|
453
508
|
|
|
454
509
|
## Contributing
|
|
455
510
|
|
|
456
|
-
Contributions are welcome! Please
|
|
511
|
+
Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.
|
|
457
512
|
|
|
458
|
-
|
|
513
|
+
### Development Setup
|
|
514
|
+
|
|
515
|
+
1. Clone the repository
|
|
516
|
+
2. Install dependencies: `bun install`
|
|
517
|
+
3. Run tests: `bun run test`
|
|
518
|
+
4. Build the project: `bun run build`
|
|
519
|
+
|
|
520
|
+
### Guidelines
|
|
521
|
+
|
|
522
|
+
- Write tests for new features
|
|
523
|
+
- Follow the existing code style
|
|
524
|
+
- Update documentation for API changes
|
|
525
|
+
- Ensure all tests pass before submitting PR
|
|
526
|
+
|
|
527
|
+
---
|
|
459
528
|
|
|
460
|
-
|
|
529
|
+
Made with ❤️ by the Ooneex team
|