@ooneex/controller 0.0.1 → 0.4.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 +373 -1
- package/dist/index.d.ts +2 -6
- package/package.json +13 -8
package/README.md
CHANGED
|
@@ -1 +1,373 @@
|
|
|
1
|
-
# @ooneex/
|
|
1
|
+
# @ooneex/controller
|
|
2
|
+
|
|
3
|
+
Base controller types and interfaces for handling HTTP requests and responses in Ooneex applications. This package provides the foundational `IController` interface and context types that define how controllers interact with requests, responses, and framework services.
|
|
4
|
+
|
|
5
|
+

|
|
6
|
+

|
|
7
|
+

|
|
8
|
+

|
|
9
|
+

|
|
10
|
+
|
|
11
|
+
## Features
|
|
12
|
+
|
|
13
|
+
✅ **Controller Interface** - Standard interface for all HTTP controllers
|
|
14
|
+
|
|
15
|
+
✅ **Rich Context** - Access to request, response, logger, cache, database, and more
|
|
16
|
+
|
|
17
|
+
✅ **Type-Safe** - Full TypeScript support with generic type parameters
|
|
18
|
+
|
|
19
|
+
✅ **Framework Integration** - Works seamlessly with routing, middleware, and DI container
|
|
20
|
+
|
|
21
|
+
✅ **Service Access** - Built-in access to analytics, storage, mailer, and other services
|
|
22
|
+
|
|
23
|
+
## Installation
|
|
24
|
+
|
|
25
|
+
### Bun
|
|
26
|
+
```bash
|
|
27
|
+
bun add @ooneex/controller
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### pnpm
|
|
31
|
+
```bash
|
|
32
|
+
pnpm add @ooneex/controller
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Yarn
|
|
36
|
+
```bash
|
|
37
|
+
yarn add @ooneex/controller
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### npm
|
|
41
|
+
```bash
|
|
42
|
+
npm install @ooneex/controller
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Usage
|
|
46
|
+
|
|
47
|
+
### Basic Controller
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
import { Route } from '@ooneex/routing';
|
|
51
|
+
import type { IController, ContextType } from '@ooneex/controller';
|
|
52
|
+
import type { IResponse } from '@ooneex/http-response';
|
|
53
|
+
|
|
54
|
+
@Route.http({
|
|
55
|
+
name: 'api.users.list',
|
|
56
|
+
path: '/api/users',
|
|
57
|
+
method: 'GET',
|
|
58
|
+
description: 'List all users'
|
|
59
|
+
})
|
|
60
|
+
class UserListController implements IController {
|
|
61
|
+
public async index(context: ContextType): Promise<IResponse> {
|
|
62
|
+
return context.response.json({
|
|
63
|
+
users: [
|
|
64
|
+
{ id: 1, name: 'John' },
|
|
65
|
+
{ id: 2, name: 'Jane' }
|
|
66
|
+
]
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Controller with Typed Configuration
|
|
73
|
+
|
|
74
|
+
```typescript
|
|
75
|
+
import { Route } from '@ooneex/routing';
|
|
76
|
+
import type { IController, ContextType, ContextConfigType } from '@ooneex/controller';
|
|
77
|
+
import type { IResponse } from '@ooneex/http-response';
|
|
78
|
+
|
|
79
|
+
interface UserShowConfig extends ContextConfigType {
|
|
80
|
+
params: { id: string };
|
|
81
|
+
queries: { include?: string };
|
|
82
|
+
payload: Record<string, never>;
|
|
83
|
+
response: { user: { id: string; name: string; email: string } };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
@Route.http({
|
|
87
|
+
name: 'api.users.show',
|
|
88
|
+
path: '/api/users/:id',
|
|
89
|
+
method: 'GET',
|
|
90
|
+
description: 'Get user by ID'
|
|
91
|
+
})
|
|
92
|
+
class UserShowController implements IController<UserShowConfig> {
|
|
93
|
+
public async index(context: ContextType<UserShowConfig>): Promise<IResponse<UserShowConfig['response']>> {
|
|
94
|
+
const { id } = context.params;
|
|
95
|
+
|
|
96
|
+
// TypeScript knows params.id is a string
|
|
97
|
+
const user = await this.findUser(id);
|
|
98
|
+
|
|
99
|
+
return context.response.json({ user });
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Accessing Context Services
|
|
105
|
+
|
|
106
|
+
```typescript
|
|
107
|
+
import { Route } from '@ooneex/routing';
|
|
108
|
+
import type { IController, ContextType } from '@ooneex/controller';
|
|
109
|
+
|
|
110
|
+
@Route.http({
|
|
111
|
+
name: 'api.products.create',
|
|
112
|
+
path: '/api/products',
|
|
113
|
+
method: 'POST',
|
|
114
|
+
description: 'Create a product'
|
|
115
|
+
})
|
|
116
|
+
class ProductCreateController implements IController {
|
|
117
|
+
public async index(context: ContextType): Promise<IResponse> {
|
|
118
|
+
const {
|
|
119
|
+
logger, // Logging service
|
|
120
|
+
analytics, // PostHog analytics
|
|
121
|
+
cache, // Redis or filesystem cache
|
|
122
|
+
storage, // File storage (Cloudflare R2, Bunny, filesystem)
|
|
123
|
+
database, // TypeORM database connection
|
|
124
|
+
mailer, // Email service (Resend, Nodemailer)
|
|
125
|
+
request, // HTTP request with parsed data
|
|
126
|
+
response, // HTTP response builder
|
|
127
|
+
params, // URL parameters
|
|
128
|
+
payload, // Request body
|
|
129
|
+
queries, // Query string parameters
|
|
130
|
+
method, // HTTP method
|
|
131
|
+
header, // Request headers
|
|
132
|
+
files, // Uploaded files
|
|
133
|
+
ip, // Client IP address
|
|
134
|
+
host, // Request host
|
|
135
|
+
language, // Detected language
|
|
136
|
+
user, // Authenticated user (if any)
|
|
137
|
+
app // Application environment
|
|
138
|
+
} = context;
|
|
139
|
+
|
|
140
|
+
// Log the action
|
|
141
|
+
logger.info('Creating product', { userId: user?.id });
|
|
142
|
+
|
|
143
|
+
// Track analytics
|
|
144
|
+
analytics?.capture({
|
|
145
|
+
id: user?.id || 'anonymous',
|
|
146
|
+
event: 'product_created'
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
// Cache the result
|
|
150
|
+
await cache?.set('latest_product', payload, 300);
|
|
151
|
+
|
|
152
|
+
return response.json({ success: true });
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## API Reference
|
|
158
|
+
|
|
159
|
+
### Interfaces
|
|
160
|
+
|
|
161
|
+
#### `IController<T>`
|
|
162
|
+
|
|
163
|
+
The main interface that all HTTP controllers must implement.
|
|
164
|
+
|
|
165
|
+
```typescript
|
|
166
|
+
interface IController<T extends ContextConfigType = ContextConfigType> {
|
|
167
|
+
index: (context: ContextType<T>) => Promise<IResponse<T['response']>> | IResponse<T['response']>;
|
|
168
|
+
}
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
**Methods:**
|
|
172
|
+
|
|
173
|
+
##### `index(context: ContextType<T>): Promise<IResponse> | IResponse`
|
|
174
|
+
|
|
175
|
+
The main handler method called when a route is matched.
|
|
176
|
+
|
|
177
|
+
**Parameters:**
|
|
178
|
+
- `context` - The request context containing all services and request data
|
|
179
|
+
|
|
180
|
+
**Returns:** HTTP response (sync or async)
|
|
181
|
+
|
|
182
|
+
### Types
|
|
183
|
+
|
|
184
|
+
#### `ContextConfigType`
|
|
185
|
+
|
|
186
|
+
Configuration type for defining controller context shape.
|
|
187
|
+
|
|
188
|
+
```typescript
|
|
189
|
+
type ContextConfigType = {
|
|
190
|
+
response: Record<string, unknown>;
|
|
191
|
+
} & RequestConfigType;
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
**Properties:**
|
|
195
|
+
- `response` - Shape of the response data
|
|
196
|
+
- `params` - URL parameter types (from RequestConfigType)
|
|
197
|
+
- `payload` - Request body types (from RequestConfigType)
|
|
198
|
+
- `queries` - Query string types (from RequestConfigType)
|
|
199
|
+
|
|
200
|
+
#### `ContextType<T>`
|
|
201
|
+
|
|
202
|
+
The full context object passed to controller methods.
|
|
203
|
+
|
|
204
|
+
```typescript
|
|
205
|
+
type ContextType<T extends ContextConfigType = ContextConfigType> = {
|
|
206
|
+
logger: ILogger<Record<string, ScalarType>> | ILogger<LogsEntity>;
|
|
207
|
+
analytics?: IAnalytics;
|
|
208
|
+
cache?: ICache;
|
|
209
|
+
storage?: IStorage;
|
|
210
|
+
database?: IDatabase;
|
|
211
|
+
mailer?: IMailer;
|
|
212
|
+
app: {
|
|
213
|
+
env: IAppEnv;
|
|
214
|
+
};
|
|
215
|
+
response: IResponse<T['response']>;
|
|
216
|
+
request: IRequest<{ params: T['params']; payload: T['payload']; queries: T['queries'] }>;
|
|
217
|
+
params: T['params'];
|
|
218
|
+
payload: T['payload'];
|
|
219
|
+
queries: T['queries'];
|
|
220
|
+
method: HttpMethodType;
|
|
221
|
+
header: Header;
|
|
222
|
+
files: Record<string, IRequestFile>;
|
|
223
|
+
ip: string | null;
|
|
224
|
+
host: string;
|
|
225
|
+
language: LocaleInfoType;
|
|
226
|
+
user: IUser | null;
|
|
227
|
+
};
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
#### `ControllerClassType`
|
|
231
|
+
|
|
232
|
+
Type for controller class constructors (used internally by the framework).
|
|
233
|
+
|
|
234
|
+
```typescript
|
|
235
|
+
type ControllerClassType = new (...args: any[]) => IController<any>;
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
## Advanced Usage
|
|
239
|
+
|
|
240
|
+
### Error Handling in Controllers
|
|
241
|
+
|
|
242
|
+
```typescript
|
|
243
|
+
import { Route } from '@ooneex/routing';
|
|
244
|
+
import { NotFoundException, BadRequestException } from '@ooneex/exception';
|
|
245
|
+
import type { IController, ContextType } from '@ooneex/controller';
|
|
246
|
+
|
|
247
|
+
@Route.http({
|
|
248
|
+
name: 'api.orders.show',
|
|
249
|
+
path: '/api/orders/:id',
|
|
250
|
+
method: 'GET',
|
|
251
|
+
description: 'Get order by ID'
|
|
252
|
+
})
|
|
253
|
+
class OrderShowController implements IController {
|
|
254
|
+
public async index(context: ContextType): Promise<IResponse> {
|
|
255
|
+
const { id } = context.params;
|
|
256
|
+
|
|
257
|
+
if (!id) {
|
|
258
|
+
throw new BadRequestException('Order ID is required');
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const order = await this.orderRepository.findById(id);
|
|
262
|
+
|
|
263
|
+
if (!order) {
|
|
264
|
+
throw new NotFoundException('Order not found', {
|
|
265
|
+
data: { orderId: id }
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return context.response.json({ order });
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
### File Upload Handling
|
|
275
|
+
|
|
276
|
+
```typescript
|
|
277
|
+
import { Route } from '@ooneex/routing';
|
|
278
|
+
import type { IController, ContextType } from '@ooneex/controller';
|
|
279
|
+
|
|
280
|
+
@Route.http({
|
|
281
|
+
name: 'api.files.upload',
|
|
282
|
+
path: '/api/files',
|
|
283
|
+
method: 'POST',
|
|
284
|
+
description: 'Upload a file'
|
|
285
|
+
})
|
|
286
|
+
class FileUploadController implements IController {
|
|
287
|
+
public async index(context: ContextType): Promise<IResponse> {
|
|
288
|
+
const { files, storage, user } = context;
|
|
289
|
+
|
|
290
|
+
const uploadedFile = files['document'];
|
|
291
|
+
|
|
292
|
+
if (!uploadedFile) {
|
|
293
|
+
return context.response.exception('No file uploaded', { status: 400 });
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Validate file type
|
|
297
|
+
if (!uploadedFile.isImage() && !uploadedFile.isPdf()) {
|
|
298
|
+
return context.response.exception('Invalid file type', { status: 400 });
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// Store the file
|
|
302
|
+
const key = `users/${user?.id}/${uploadedFile.name}`;
|
|
303
|
+
await storage?.putFile(key, uploadedFile.path);
|
|
304
|
+
|
|
305
|
+
return context.response.json({
|
|
306
|
+
uploaded: true,
|
|
307
|
+
key,
|
|
308
|
+
size: uploadedFile.size,
|
|
309
|
+
type: uploadedFile.type
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
### Language-Aware Responses
|
|
316
|
+
|
|
317
|
+
```typescript
|
|
318
|
+
import { Route } from '@ooneex/routing';
|
|
319
|
+
import type { IController, ContextType } from '@ooneex/controller';
|
|
320
|
+
|
|
321
|
+
@Route.http({
|
|
322
|
+
name: 'api.greetings.get',
|
|
323
|
+
path: '/api/greetings',
|
|
324
|
+
method: 'GET',
|
|
325
|
+
description: 'Get greeting in user language'
|
|
326
|
+
})
|
|
327
|
+
class GreetingController implements IController {
|
|
328
|
+
private greetings: Record<string, string> = {
|
|
329
|
+
en: 'Hello!',
|
|
330
|
+
fr: 'Bonjour!',
|
|
331
|
+
es: '¡Hola!',
|
|
332
|
+
de: 'Hallo!'
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
public async index(context: ContextType): Promise<IResponse> {
|
|
336
|
+
const { language } = context;
|
|
337
|
+
|
|
338
|
+
const greeting = this.greetings[language.code] || this.greetings.en;
|
|
339
|
+
|
|
340
|
+
return context.response.json({
|
|
341
|
+
greeting,
|
|
342
|
+
language: language.code,
|
|
343
|
+
region: language.region
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
## License
|
|
350
|
+
|
|
351
|
+
This project is licensed under the MIT License - see the [LICENSE](./LICENSE) file for details.
|
|
352
|
+
|
|
353
|
+
## Contributing
|
|
354
|
+
|
|
355
|
+
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.
|
|
356
|
+
|
|
357
|
+
### Development Setup
|
|
358
|
+
|
|
359
|
+
1. Clone the repository
|
|
360
|
+
2. Install dependencies: `bun install`
|
|
361
|
+
3. Run tests: `bun run test`
|
|
362
|
+
4. Build the project: `bun run build`
|
|
363
|
+
|
|
364
|
+
### Guidelines
|
|
365
|
+
|
|
366
|
+
- Write tests for new features
|
|
367
|
+
- Follow the existing code style
|
|
368
|
+
- Update documentation for API changes
|
|
369
|
+
- Ensure all tests pass before submitting PR
|
|
370
|
+
|
|
371
|
+
---
|
|
372
|
+
|
|
373
|
+
Made with ❤️ by the Ooneex team
|
package/dist/index.d.ts
CHANGED
|
@@ -1,19 +1,18 @@
|
|
|
1
1
|
import { IAnalytics } from "@ooneex/analytics";
|
|
2
2
|
import { IAppEnv } from "@ooneex/app-env";
|
|
3
3
|
import { ICache } from "@ooneex/cache";
|
|
4
|
-
import { IDatabase
|
|
4
|
+
import { IDatabase } from "@ooneex/database";
|
|
5
5
|
import { Header } from "@ooneex/http-header";
|
|
6
6
|
import { IRequest, RequestConfigType } from "@ooneex/http-request";
|
|
7
7
|
import { IRequestFile } from "@ooneex/http-request-file";
|
|
8
8
|
import { IResponse } from "@ooneex/http-response";
|
|
9
9
|
import { ILogger, LogsEntity } from "@ooneex/logger";
|
|
10
10
|
import { IMailer } from "@ooneex/mailer";
|
|
11
|
-
import { IPermission } from "@ooneex/permission";
|
|
12
11
|
import { IStorage } from "@ooneex/storage";
|
|
13
12
|
import { LocaleInfoType } from "@ooneex/translation";
|
|
14
13
|
import { HttpMethodType, ScalarType } from "@ooneex/types";
|
|
15
14
|
import { IUser } from "@ooneex/user";
|
|
16
|
-
type ControllerClassType = new (...args: any[]) => IController
|
|
15
|
+
type ControllerClassType = new (...args: any[]) => IController<any>;
|
|
17
16
|
interface IController<T extends ContextConfigType = ContextConfigType> {
|
|
18
17
|
index: (context: ContextType<T>) => Promise<IResponse<T["response"]>> | IResponse<T["response"]>;
|
|
19
18
|
}
|
|
@@ -24,13 +23,10 @@ type ContextType<T extends ContextConfigType = ContextConfigType> = {
|
|
|
24
23
|
logger: ILogger<Record<string, ScalarType>> | ILogger<LogsEntity>;
|
|
25
24
|
analytics?: IAnalytics;
|
|
26
25
|
cache?: ICache;
|
|
27
|
-
permission?: IPermission;
|
|
28
26
|
storage?: IStorage;
|
|
29
27
|
database?: IDatabase;
|
|
30
|
-
redis?: IRedisDatabaseAdapter;
|
|
31
28
|
mailer?: IMailer;
|
|
32
29
|
app: {
|
|
33
|
-
url: string;
|
|
34
30
|
env: IAppEnv;
|
|
35
31
|
};
|
|
36
32
|
response: IResponse<T["response"]>;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ooneex/controller",
|
|
3
|
-
"description": "",
|
|
4
|
-
"version": "0.0
|
|
3
|
+
"description": "Base controller classes and decorators for handling HTTP requests and responses in Ooneex applications",
|
|
4
|
+
"version": "0.4.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
7
7
|
"dist",
|
|
@@ -24,11 +24,8 @@
|
|
|
24
24
|
"scripts": {
|
|
25
25
|
"build": "bunup",
|
|
26
26
|
"lint": "tsgo --noEmit && bunx biome lint",
|
|
27
|
-
"publish
|
|
28
|
-
"publish:pack": "bun pm pack --destination ./dist",
|
|
29
|
-
"publish:dry": "bun publish --dry-run"
|
|
27
|
+
"npm:publish": "bun publish --tolerate-republish --access public"
|
|
30
28
|
},
|
|
31
|
-
"dependencies": {},
|
|
32
29
|
"devDependencies": {
|
|
33
30
|
"@ooneex/analytics": "0.0.1",
|
|
34
31
|
"@ooneex/app-env": "0.0.1",
|
|
@@ -40,11 +37,19 @@
|
|
|
40
37
|
"@ooneex/http-response": "0.0.1",
|
|
41
38
|
"@ooneex/logger": "0.0.1",
|
|
42
39
|
"@ooneex/mailer": "0.0.1",
|
|
43
|
-
"@ooneex/permission": "0.0.1",
|
|
44
40
|
"@ooneex/storage": "0.0.1",
|
|
45
41
|
"@ooneex/translation": "0.0.1",
|
|
46
42
|
"@ooneex/types": "0.0.1",
|
|
47
43
|
"@ooneex/user": "0.0.1"
|
|
48
44
|
},
|
|
49
|
-
"
|
|
45
|
+
"keywords": [
|
|
46
|
+
"bun",
|
|
47
|
+
"controller",
|
|
48
|
+
"decorator",
|
|
49
|
+
"http",
|
|
50
|
+
"mvc",
|
|
51
|
+
"ooneex",
|
|
52
|
+
"routing",
|
|
53
|
+
"typescript"
|
|
54
|
+
]
|
|
50
55
|
}
|