@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 CHANGED
@@ -1,460 +1,529 @@
1
1
  # @ooneex/routing
2
2
 
3
- Type-safe routing system for Ooneex applications with powerful utilities for route configuration and type generation.
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
+ ![Bun](https://img.shields.io/badge/Bun-Compatible-orange?style=flat-square&logo=bun)
6
+ ![Deno](https://img.shields.io/badge/Deno-Compatible-blue?style=flat-square&logo=deno)
7
+ ![Node.js](https://img.shields.io/badge/Node.js-Compatible-green?style=flat-square&logo=node.js)
8
+ ![TypeScript](https://img.shields.io/badge/TypeScript-Ready-blue?style=flat-square&logo=typescript)
9
+ ![MIT License](https://img.shields.io/badge/License-MIT-yellow?style=flat-square)
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
- ## Features
38
+ ### pnpm
39
+ ```bash
40
+ pnpm add @ooneex/routing
41
+ ```
12
42
 
13
- - 🎯 Type-safe route definitions with path parameter extraction
14
- - 🔒 Compile-time route validation
15
- - 🚀 Decorator-based route registration
16
- - 🔄 Automatic type inference from ArkType schemas
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
- ## Quick Start
48
+ ### npm
49
+ ```bash
50
+ npm install @ooneex/routing
51
+ ```
22
52
 
23
- ```typescript
24
- import { Route, type ContextType } from "@ooneex/routing";
25
- import { Assert } from "@ooneex/validation";
53
+ ## Usage
26
54
 
27
- @Route.get("/users/:id", {
28
- name: "api.users.show",
29
- description: "Get a user by ID",
30
- params: {
31
- id: Assert("string"),
32
- },
33
- response: Assert({
34
- id: "string",
35
- name: "string",
36
- email: "string",
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
- export class GetUserController {
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
- ## Route Configuration
77
+ ### Route with Parameters
54
78
 
55
- ### Route Decorators
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
- The package provides decorators for all HTTP methods and WebSocket connections:
102
+ ### Route with Query Validation
58
103
 
59
- - `@Route.get(path, config)`
60
- - `@Route.post(path, config)`
61
- - `@Route.put(path, config)`
62
- - `@Route.patch(path, config)`
63
- - `@Route.delete(path, config)`
64
- - `@Route.options(path, config)`
65
- - `@Route.head(path, config)`
66
- - `@Route.socket(path, config)` - for WebSocket routes
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 Configuration Options
131
+ ### Route with Payload Validation
69
132
 
70
133
  ```typescript
71
- type RouteConfig = {
72
- name: RouteNameType; // e.g., "api.users.list"
73
- description: string; // Route description
74
- params?: Record<string, AssertType | IAssert>; // Path parameters
75
- queries?: AssertType | IAssert; // Query string validation
76
- payload?: AssertType | IAssert; // Request body validation
77
- response?: AssertType | IAssert; // Response validation
78
- env?: Environment[]; // Allowed environments
79
- ip?: string[]; // IP whitelist
80
- host?: string[]; // Host whitelist
81
- roles?: ERole[]; // Required roles
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
- ### Path Parameters
86
-
87
- Routes support dynamic path parameters that are automatically extracted and validated:
159
+ ### WebSocket Route
88
160
 
89
161
  ```typescript
90
- @Route.delete("/users/:userId/posts/:postId", {
91
- name: "api.users.posts.delete",
92
- description: "Delete a user's post",
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
- userId: Assert("string"),
95
- postId: Assert("string"),
96
- },
97
- response: Assert({
98
- success: "boolean",
99
- message: "string",
100
- }),
170
+ roomId: type('string')
171
+ }
101
172
  })
102
- export class DeleteUserPostController {
103
- public async index(context: ContextType) {
104
- const { userId, postId } = context.params;
105
- // Delete logic here
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
- ## Utilities
187
+ ### Route with Role-Based Access
111
188
 
112
- ### `routeConfigToTypeString(config)`
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
- Convert a route configuration to a TypeScript type string representation. This is useful for code generation, documentation, and type visualization.
209
+ ### Generating URLs
115
210
 
116
- #### Parameters
211
+ ```typescript
212
+ import { router } from '@ooneex/routing';
117
213
 
118
- - `config`: Object containing route configuration with `params`, `queries`, `payload`, and/or `response` fields
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
- #### Returns
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
- A formatted string representing the TypeScript type definition.
226
+ ## API Reference
123
227
 
124
- #### Example
228
+ ### Decorators
125
229
 
126
- ```typescript
127
- import { routeConfigToTypeString } from "@ooneex/routing";
128
- import { type } from "arktype";
230
+ #### `@Route.http(config: RouteConfigType)`
129
231
 
130
- const config = {
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
- const typeString = routeConfigToTypeString(config);
154
- console.log(typeString);
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
- **Output:**
251
+ **Example:**
158
252
  ```typescript
159
- {
160
- response: { success: boolean; message: string; data: { id: string; name: string } };
161
- params: { id: string; emailId: string };
162
- payload: { name: string; email: string };
163
- queries: { limit: number; offset: number };
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
- #### Use Cases
264
+ #### `@Route.socket(config: RouteConfigType)`
168
265
 
169
- **1. Type Definition Generation**
266
+ Decorator for defining WebSocket routes.
170
267
 
171
- ```typescript
172
- const typeName = "DeleteUserRouteConfig";
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
- **2. API Client Generation**
271
+ ---
185
272
 
186
- ```typescript
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
- const clientMethod = `
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
- **3. Documentation Generation**
277
+ Main router class for managing routes.
201
278
 
202
- ```typescript
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
- ### `isValidRoutePath(path)`
281
+ ##### `addRoute(route: RouteConfigType): this`
215
282
 
216
- Validates a route path at runtime.
283
+ Manually add a route to the router.
217
284
 
218
- ```typescript
219
- import { isValidRoutePath } from "@ooneex/routing";
285
+ **Parameters:**
286
+ - `route` - The route configuration
220
287
 
221
- isValidRoutePath("/users/:id"); // true
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
- ### `extractParameterNames(path)`
290
+ **Throws:** `RouterException` if route name or path/method already exists
229
291
 
230
- Extract parameter names from a route path.
292
+ ##### `findRouteByPath(path: string): RouteConfigType[] | null`
231
293
 
232
- ```typescript
233
- import { extractParameterNames } from "@ooneex/routing";
294
+ Find all routes registered for a specific path.
234
295
 
235
- extractParameterNames("/users/:id");
236
- // ["id"]
296
+ **Parameters:**
297
+ - `path` - The URL path
237
298
 
238
- extractParameterNames("/users/:userId/posts/:postId");
239
- // ["userId", "postId"]
299
+ **Returns:** Array of route configurations or null
240
300
 
241
- extractParameterNames("/static/path");
242
- // []
243
- ```
301
+ ##### `findRouteByName(name: RouteNameType): RouteConfigType | null`
244
302
 
245
- ## Type System
303
+ Find a route by its unique name.
246
304
 
247
- ### Route Path Types
305
+ **Parameters:**
306
+ - `name` - The route name
248
307
 
249
- The package provides compile-time validation for route paths:
308
+ **Returns:** Route configuration or null
250
309
 
251
- ```typescript
252
- import type { RoutePathType, ExtractParameters } from "@ooneex/routing";
310
+ ##### `getRoutes(): Map<string, RouteConfigType[]>`
253
311
 
254
- // Valid paths
255
- type ValidPath = RoutePathType<"/users/:id">;
256
- type MultiParam = RoutePathType<"/users/:userId/posts/:postId">;
312
+ Get all registered routes.
257
313
 
258
- // Extract parameters
259
- type UserParams = ExtractParameters<"/users/:id">;
260
- // Result: "id"
314
+ **Returns:** Map of path to route configurations
261
315
 
262
- type PostParams = ExtractParameters<"/users/:userId/posts/:postId">;
263
- // Result: "userId" | "postId"
264
- ```
316
+ ##### `getHttpRoutes(): Map<string, RouteConfigType[]>`
265
317
 
266
- ### Route Name Types
318
+ Get only HTTP routes (excludes WebSocket routes).
267
319
 
268
- Route names follow a strict pattern: `namespace.resource.action`
320
+ **Returns:** Map of path to HTTP route configurations
269
321
 
270
- ```typescript
271
- import type { RouteNameType } from "@ooneex/routing";
322
+ ##### `getSocketRoutes(): Map<string, RouteConfigType>`
272
323
 
273
- // Valid route names
274
- type ApiUserList = "api.users.list";
275
- type ApiUserShow = "api.users.show";
276
- type WebPostCreate = "client.posts.create";
277
- type AdminSettingsUpdate = "admin.settings.update";
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
- ## Router API
336
+ **Returns:** The generated URL path
281
337
 
282
- ### Adding Routes
338
+ **Throws:** `RouterException` if route not found or missing required parameters
283
339
 
340
+ **Example:**
284
341
  ```typescript
285
- import { router } from "@ooneex/routing";
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
- ### Finding Routes
345
+ ### Types
346
+
347
+ #### `RouteNameType`
348
+
349
+ Route name following the `namespace.resource.action` pattern.
298
350
 
299
351
  ```typescript
300
- // Find by path
301
- const routes = router.findRouteByPath("/users/:id");
352
+ type RouteNameType = `${RouteNamespace}.${string}.${RouteAction}`;
353
+ // Example: "api.users.list", "admin.products.create"
354
+ ```
302
355
 
303
- // Find by name
304
- const route = router.findRouteByName("api.users.show");
356
+ **Valid Namespaces:**
357
+ - `api`, `client`, `admin`, `public`, `auth`, `webhook`, `internal`, `external`, `system`, `health`, `metrics`, `docs`
305
358
 
306
- // Get all routes
307
- const allRoutes = router.getRoutes();
359
+ **Valid Actions:**
360
+ - `list`, `show`, `create`, `update`, `delete`, `search`, `export`, `import`, and 200+ more
308
361
 
309
- // Get only HTTP routes
310
- const httpRoutes = router.getHttpRoutes();
362
+ #### `RouteConfigType`
311
363
 
312
- // Get only WebSocket routes
313
- const socketRoutes = router.getSocketRoutes();
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
- ### Generating URLs
390
+ #### `ExtractParameters<T>`
391
+
392
+ Utility type to extract parameter names from a route path.
317
393
 
318
394
  ```typescript
319
- // Generate URL from route name
320
- const url = router.generate("api.users.show", { id: "123" });
321
- // Result: "/users/123"
395
+ type Params = ExtractParameters<'/users/:id/orders/:orderId'>;
396
+ // Result: "id" | "orderId"
397
+ ```
322
398
 
323
- const complexUrl = router.generate("api.users.posts.show", {
324
- userId: "123",
325
- postId: "456",
326
- });
327
- // Result: "/users/123/posts/456"
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 Examples
408
+ ## Advanced Usage
331
409
 
332
- ### Complex Route with All Options
410
+ ### Environment-Specific Routes
333
411
 
334
412
  ```typescript
335
- import { Route } from "@ooneex/routing";
336
- import { Assert } from "@ooneex/validation";
337
- import { Environment } from "@ooneex/app-env";
338
- import { ERole } from "@ooneex/role";
339
-
340
- @Route.post("/admin/users/:userId/permissions", {
341
- name: "admin.users.permissions.update",
342
- description: "Update user permissions (admin only)",
343
- params: {
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
- export class UpdateUserPermissionsController {
362
- public async index(context: ContextType) {
363
- // Implementation
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
- ### WebSocket Route
433
+ ### Custom Permission Classes
369
434
 
370
435
  ```typescript
371
- @Route.socket("/chat/:roomId", {
372
- name: "client.chat.room",
373
- description: "WebSocket chat room connection",
374
- params: {
375
- roomId: Assert("string"),
376
- },
377
- queries: Assert({
378
- token: "string",
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
- ### Optional Properties
459
+ ### IP Address Restrictions
389
460
 
390
461
  ```typescript
391
- @Route.patch("/users/:id", {
392
- name: "api.users.update",
393
- description: "Update user profile",
394
- params: {
395
- id: Assert("string"),
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
- export class UpdateUserController {
409
- public async index(context: ContextType) {
410
- // Update logic
411
- }
469
+ class HealthCheckController implements IController {
470
+ // Only accessible from specified IP ranges
412
471
  }
413
472
  ```
414
473
 
415
- ## Testing
474
+ ### Code Generation Options
416
475
 
417
476
  ```typescript
418
- import { describe, expect, test } from "bun:test";
419
- import { routeConfigToTypeString, extractParameterNames } from "@ooneex/routing";
420
- import { type } from "arktype";
421
-
422
- describe("Route utilities", () => {
423
- test("converts route config to type string", () => {
424
- const config = {
425
- params: { id: type("string") },
426
- response: type({ success: "boolean" }),
427
- };
428
-
429
- const result = routeConfigToTypeString(config);
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
- ## Best Practices
491
+ ### Error Handling
445
492
 
446
- 1. **Use descriptive route names** following the `namespace.resource.action` pattern
447
- 2. **Validate all inputs** using ArkType assertions for params, queries, and payload
448
- 3. **Document routes** with clear descriptions
449
- 4. **Type your responses** to ensure API consistency
450
- 5. **Use environment restrictions** for sensitive endpoints
451
- 6. **Leverage path parameter extraction** for type-safe access to URL parameters
452
- 7. **Generate types** using `routeConfigToTypeString` for API clients and documentation
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 follow the project's coding standards and ensure all tests pass.
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
- ## License
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
- MIT
529
+ Made with ❤️ by the Ooneex team