alepha 0.9.4 → 0.9.5
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/LICENSE +21 -21
- package/README.md +93 -93
- package/batch.d.ts +489 -13
- package/bucket.d.ts +446 -9
- package/cache.d.ts +132 -1
- package/command.d.ts +2 -0
- package/core.d.ts +101 -53
- package/email.cjs +8 -0
- package/email.d.ts +246 -0
- package/email.js +1 -0
- package/lock.d.ts +412 -23
- package/logger.d.ts +42 -37
- package/package.json +50 -43
- package/postgres.d.ts +1670 -108
- package/queue.d.ts +651 -2
- package/react/auth.d.ts +109 -112
- package/react/form.d.ts +11 -4
- package/react/head.d.ts +1 -1
- package/react/i18n.d.ts +5 -2
- package/react.d.ts +219 -174
- package/redis.d.ts +6 -6
- package/router.d.ts +1 -0
- package/security.d.ts +5 -5
- package/server/links.d.ts +40 -29
- package/server/proxy.d.ts +192 -0
- package/server.d.ts +601 -29
- package/topic.d.ts +803 -11
- package/vite.d.ts +8 -4
package/server.d.ts
CHANGED
|
@@ -7,7 +7,7 @@ import { Route, RouterProvider } from "alepha/router";
|
|
|
7
7
|
import * as _alepha_cache0 from "alepha/cache";
|
|
8
8
|
import { IncomingMessage, ServerResponse as ServerResponse$1 } from "node:http";
|
|
9
9
|
import { DateTimeProvider, DurationLike } from "alepha/datetime";
|
|
10
|
-
import * as
|
|
10
|
+
import * as _sinclair_typebox0 from "@sinclair/typebox";
|
|
11
11
|
import * as http0 from "http";
|
|
12
12
|
|
|
13
13
|
//#region src/constants/routeMethods.d.ts
|
|
@@ -15,15 +15,31 @@ declare const routeMethods: readonly ["GET", "POST", "PUT", "PATCH", "DELETE", "
|
|
|
15
15
|
type RouteMethod = (typeof routeMethods)[number];
|
|
16
16
|
//#endregion
|
|
17
17
|
//#region src/helpers/ServerReply.d.ts
|
|
18
|
+
/**
|
|
19
|
+
* Helper for building server replies.
|
|
20
|
+
*/
|
|
18
21
|
declare class ServerReply {
|
|
19
22
|
headers: Record<string, string> & {
|
|
20
23
|
"set-cookie"?: string[];
|
|
21
24
|
};
|
|
22
25
|
status?: number;
|
|
23
26
|
body?: any;
|
|
27
|
+
/**
|
|
28
|
+
* Redirect to a given URL with optional status code (default 302).
|
|
29
|
+
*/
|
|
24
30
|
redirect(url: string, status?: number): void;
|
|
25
|
-
|
|
26
|
-
|
|
31
|
+
/**
|
|
32
|
+
* Set the response status code.
|
|
33
|
+
*/
|
|
34
|
+
setStatus(status: number): this;
|
|
35
|
+
/**
|
|
36
|
+
* Set a response header.
|
|
37
|
+
*/
|
|
38
|
+
setHeader(name: string, value: string): this;
|
|
39
|
+
/**
|
|
40
|
+
* Set the response body.
|
|
41
|
+
*/
|
|
42
|
+
setBody(body: any): this;
|
|
27
43
|
}
|
|
28
44
|
//#endregion
|
|
29
45
|
//#region src/services/UserAgentParser.d.ts
|
|
@@ -60,7 +76,16 @@ type ServerRequestConfigEntry<TConfig extends RequestConfigSchema = RequestConfi
|
|
|
60
76
|
interface ServerRequest<TConfig extends RequestConfigSchema = RequestConfigSchema> extends ServerRequestConfig<TConfig> {
|
|
61
77
|
method: RouteMethod;
|
|
62
78
|
url: URL;
|
|
79
|
+
requestId: string;
|
|
80
|
+
/**
|
|
81
|
+
* Client IP address.
|
|
82
|
+
* Will parse `X-Forwarded-For` header if present.
|
|
83
|
+
*/
|
|
63
84
|
ip?: string;
|
|
85
|
+
/**
|
|
86
|
+
* Value of the `Host` header sent by the client.
|
|
87
|
+
*/
|
|
88
|
+
host?: string;
|
|
64
89
|
/**
|
|
65
90
|
* Browser user agent information.
|
|
66
91
|
* Information are not guaranteed to be accurate. Use with caution.
|
|
@@ -69,10 +94,26 @@ interface ServerRequest<TConfig extends RequestConfigSchema = RequestConfigSchem
|
|
|
69
94
|
*/
|
|
70
95
|
userAgent: UserAgentInfo;
|
|
71
96
|
metadata: Record<string, any>;
|
|
97
|
+
/**
|
|
98
|
+
* Reply object to be used to send response.
|
|
99
|
+
*/
|
|
72
100
|
reply: ServerReply;
|
|
101
|
+
/**
|
|
102
|
+
* Raw request and response objects from platform.
|
|
103
|
+
* You should avoid using this property as much as possible to keep your code platform-agnostic.
|
|
104
|
+
*/
|
|
73
105
|
raw: {
|
|
106
|
+
/**
|
|
107
|
+
* Node.js request and response objects.
|
|
108
|
+
*/
|
|
74
109
|
node?: {
|
|
110
|
+
/**
|
|
111
|
+
* Node.js IncomingMessage object. (request)
|
|
112
|
+
*/
|
|
75
113
|
req: IncomingMessage;
|
|
114
|
+
/**
|
|
115
|
+
* Node.js ServerResponse object. (response)
|
|
116
|
+
*/
|
|
76
117
|
res: ServerResponse$1;
|
|
77
118
|
};
|
|
78
119
|
};
|
|
@@ -96,8 +137,9 @@ interface ServerResponse {
|
|
|
96
137
|
headers: Record<string, string>;
|
|
97
138
|
status: number;
|
|
98
139
|
}
|
|
140
|
+
type ServerRouteRequestHandler = (request: ServerRawRequest) => Promise<ServerResponse>;
|
|
99
141
|
interface ServerRouteMatcher extends Route {
|
|
100
|
-
handler:
|
|
142
|
+
handler: ServerRouteRequestHandler;
|
|
101
143
|
}
|
|
102
144
|
interface ServerRawRequest {
|
|
103
145
|
method: RouteMethod;
|
|
@@ -120,11 +162,24 @@ declare abstract class ServerProvider {
|
|
|
120
162
|
protected isViteNotFound(url?: string, route?: Route, params?: Record<string, string>): boolean;
|
|
121
163
|
}
|
|
122
164
|
//#endregion
|
|
165
|
+
//#region src/services/ServerRequestParser.d.ts
|
|
166
|
+
declare class ServerRequestParser {
|
|
167
|
+
protected readonly alepha: Alepha;
|
|
168
|
+
protected readonly userAgentParser: UserAgentParser;
|
|
169
|
+
createServerRequest(rawRequest: ServerRawRequest): ServerRequest;
|
|
170
|
+
getRequestId(request: ServerRawRequest): string | undefined;
|
|
171
|
+
getRequestUserAgent(request: ServerRawRequest): UserAgentInfo;
|
|
172
|
+
getRequestIp(request: ServerRawRequest): string | undefined;
|
|
173
|
+
}
|
|
174
|
+
//#endregion
|
|
123
175
|
//#region src/providers/ServerTimingProvider.d.ts
|
|
124
176
|
type TimingMap = Record<string, [number, number]>;
|
|
125
177
|
declare class ServerTimingProvider {
|
|
126
178
|
protected readonly log: _alepha_logger0.Logger;
|
|
127
179
|
protected readonly alepha: Alepha;
|
|
180
|
+
options: {
|
|
181
|
+
disabled: boolean;
|
|
182
|
+
};
|
|
128
183
|
readonly onRequest: _alepha_core1.HookDescriptor<"server:onRequest">;
|
|
129
184
|
readonly onResponse: _alepha_core1.HookDescriptor<"server:onResponse">;
|
|
130
185
|
protected get handlerName(): string;
|
|
@@ -145,11 +200,9 @@ declare class ServerRouterProvider extends RouterProvider<ServerRouteMatcher> {
|
|
|
145
200
|
protected readonly alepha: Alepha;
|
|
146
201
|
protected readonly routes: ServerRoute[];
|
|
147
202
|
protected readonly serverTimingProvider: ServerTimingProvider;
|
|
148
|
-
protected readonly
|
|
203
|
+
protected readonly serverRequestParser: ServerRequestParser;
|
|
149
204
|
getRoutes(): ServerRoute[];
|
|
150
205
|
createRoute<TConfig extends RequestConfigSchema = RequestConfigSchema>(route: ServerRoute<TConfig>): void;
|
|
151
|
-
onRequest(route: ServerRoute, rawRequest: ServerRawRequest, responseKind: ResponseKind): Promise<ServerResponse>;
|
|
152
|
-
protected getClientIp(request: ServerRawRequest): string | undefined;
|
|
153
206
|
protected processRequest(request: ServerRequest, route: ServerRoute, responseKind: ResponseKind): Promise<{
|
|
154
207
|
status: number;
|
|
155
208
|
headers: Record<string, string> & {
|
|
@@ -241,21 +294,540 @@ interface HttpAction {
|
|
|
241
294
|
//#endregion
|
|
242
295
|
//#region src/descriptors/$action.d.ts
|
|
243
296
|
/**
|
|
244
|
-
*
|
|
297
|
+
* Creates a server action descriptor for defining type-safe HTTP endpoints.
|
|
298
|
+
*
|
|
299
|
+
* Server actions are the core building blocks for REST APIs in the Alepha framework. They provide
|
|
300
|
+
* a declarative way to define HTTP endpoints with full TypeScript type safety, automatic schema
|
|
301
|
+
* validation, and integrated security features. Actions automatically handle routing, request
|
|
302
|
+
* parsing, response serialization, and OpenAPI documentation generation.
|
|
303
|
+
*
|
|
304
|
+
* **Key Features**
|
|
305
|
+
*
|
|
306
|
+
* - **Type Safety**: Full TypeScript inference for request/response types
|
|
307
|
+
* - **Schema Validation**: Automatic validation using TypeBox schemas
|
|
308
|
+
* - **Auto-routing**: Convention-based URL generation with customizable paths
|
|
309
|
+
* - **Multiple Invocation**: Call directly (`run()`) or via HTTP (`fetch()`)
|
|
310
|
+
* - **OpenAPI Integration**: Automatic documentation generation
|
|
311
|
+
* - **Security Integration**: Built-in authentication and authorization support
|
|
312
|
+
* - **Content Type Detection**: Automatic handling of JSON, form-data, and plain text
|
|
313
|
+
*
|
|
314
|
+
* **URL Generation**
|
|
315
|
+
*
|
|
316
|
+
* By default, actions are prefixed with `/api` (configurable via `SERVER_API_PREFIX`):
|
|
317
|
+
* - Property name becomes the endpoint path
|
|
318
|
+
* - Path parameters are automatically detected from schema
|
|
319
|
+
* - HTTP method defaults to GET, or POST if body schema is provided
|
|
320
|
+
*
|
|
321
|
+
* **Use Cases**
|
|
322
|
+
*
|
|
323
|
+
* Perfect for building robust REST APIs:
|
|
324
|
+
* - CRUD operations with full type safety
|
|
325
|
+
* - File upload and download endpoints
|
|
326
|
+
* - Real-time data processing APIs
|
|
327
|
+
* - Integration with external services
|
|
328
|
+
* - Microservice communication
|
|
329
|
+
* - Admin and management interfaces
|
|
330
|
+
*
|
|
331
|
+
* @example
|
|
332
|
+
* **Basic CRUD operations:**
|
|
333
|
+
* ```ts
|
|
334
|
+
* import { $action } from "alepha/server";
|
|
335
|
+
* import { t } from "alepha";
|
|
336
|
+
*
|
|
337
|
+
* class UserController {
|
|
338
|
+
* // GET /api/users
|
|
339
|
+
* getUsers = $action({
|
|
340
|
+
* description: "Retrieve all users with pagination",
|
|
341
|
+
* schema: {
|
|
342
|
+
* query: t.object({
|
|
343
|
+
* page: t.optional(t.number({ default: 1 })),
|
|
344
|
+
* limit: t.optional(t.number({ default: 10, maximum: 100 })),
|
|
345
|
+
* search: t.optional(t.string())
|
|
346
|
+
* }),
|
|
347
|
+
* response: t.object({
|
|
348
|
+
* users: t.array(t.object({
|
|
349
|
+
* id: t.string(),
|
|
350
|
+
* name: t.string(),
|
|
351
|
+
* email: t.string(),
|
|
352
|
+
* createdAt: t.datetime()
|
|
353
|
+
* })),
|
|
354
|
+
* total: t.number(),
|
|
355
|
+
* hasMore: t.boolean()
|
|
356
|
+
* })
|
|
357
|
+
* },
|
|
358
|
+
* handler: async ({ query }) => {
|
|
359
|
+
* const { page, limit, search } = query;
|
|
360
|
+
* const users = await this.userService.findUsers({ page, limit, search });
|
|
361
|
+
*
|
|
362
|
+
* return {
|
|
363
|
+
* users: users.items,
|
|
364
|
+
* total: users.total,
|
|
365
|
+
* hasMore: (page * limit) < users.total
|
|
366
|
+
* };
|
|
367
|
+
* }
|
|
368
|
+
* });
|
|
369
|
+
*
|
|
370
|
+
* // POST /api/users
|
|
371
|
+
* createUser = $action({
|
|
372
|
+
* description: "Create a new user account",
|
|
373
|
+
* schema: {
|
|
374
|
+
* body: t.object({
|
|
375
|
+
* name: t.string({ minLength: 2, maxLength: 100 }),
|
|
376
|
+
* email: t.string({ format: "email" }),
|
|
377
|
+
* password: t.string({ minLength: 8 }),
|
|
378
|
+
* role: t.optional(t.enum(["user", "admin"]))
|
|
379
|
+
* }),
|
|
380
|
+
* response: t.object({
|
|
381
|
+
* id: t.string(),
|
|
382
|
+
* name: t.string(),
|
|
383
|
+
* email: t.string(),
|
|
384
|
+
* role: t.string(),
|
|
385
|
+
* createdAt: t.datetime()
|
|
386
|
+
* })
|
|
387
|
+
* },
|
|
388
|
+
* handler: async ({ body }) => {
|
|
389
|
+
* // Password validation and hashing
|
|
390
|
+
* await this.authService.validatePassword(body.password);
|
|
391
|
+
* const hashedPassword = await this.authService.hashPassword(body.password);
|
|
392
|
+
*
|
|
393
|
+
* // Create user with default role
|
|
394
|
+
* const user = await this.userService.create({
|
|
395
|
+
* ...body,
|
|
396
|
+
* password: hashedPassword,
|
|
397
|
+
* role: body.role || "user"
|
|
398
|
+
* });
|
|
399
|
+
*
|
|
400
|
+
* // Return user without password
|
|
401
|
+
* const { password, ...publicUser } = user;
|
|
402
|
+
* return publicUser;
|
|
403
|
+
* }
|
|
404
|
+
* });
|
|
405
|
+
*
|
|
406
|
+
* // GET /api/users/:id
|
|
407
|
+
* getUser = $action({
|
|
408
|
+
* description: "Retrieve user by ID",
|
|
409
|
+
* schema: {
|
|
410
|
+
* params: t.object({
|
|
411
|
+
* id: t.string()
|
|
412
|
+
* }),
|
|
413
|
+
* response: t.object({
|
|
414
|
+
* id: t.string(),
|
|
415
|
+
* name: t.string(),
|
|
416
|
+
* email: t.string(),
|
|
417
|
+
* role: t.string(),
|
|
418
|
+
* profile: t.optional(t.object({
|
|
419
|
+
* bio: t.string(),
|
|
420
|
+
* avatar: t.string({ format: "uri" }),
|
|
421
|
+
* location: t.string()
|
|
422
|
+
* }))
|
|
423
|
+
* })
|
|
424
|
+
* },
|
|
425
|
+
* handler: async ({ params }) => {
|
|
426
|
+
* const user = await this.userService.findById(params.id);
|
|
427
|
+
* if (!user) {
|
|
428
|
+
* throw new Error(`User not found: ${params.id}`);
|
|
429
|
+
* }
|
|
430
|
+
* return user;
|
|
431
|
+
* }
|
|
432
|
+
* });
|
|
433
|
+
*
|
|
434
|
+
* // PUT /api/users/:id
|
|
435
|
+
* updateUser = $action({
|
|
436
|
+
* method: "PUT",
|
|
437
|
+
* description: "Update user information",
|
|
438
|
+
* schema: {
|
|
439
|
+
* params: t.object({ id: t.string() }),
|
|
440
|
+
* body: t.object({
|
|
441
|
+
* name: t.optional(t.string({ minLength: 2 })),
|
|
442
|
+
* email: t.optional(t.string({ format: "email" })),
|
|
443
|
+
* profile: t.optional(t.object({
|
|
444
|
+
* bio: t.optional(t.string()),
|
|
445
|
+
* avatar: t.optional(t.string({ format: "uri" })),
|
|
446
|
+
* location: t.optional(t.string())
|
|
447
|
+
* }))
|
|
448
|
+
* }),
|
|
449
|
+
* response: t.object({
|
|
450
|
+
* id: t.string(),
|
|
451
|
+
* name: t.string(),
|
|
452
|
+
* email: t.string(),
|
|
453
|
+
* updatedAt: t.datetime()
|
|
454
|
+
* })
|
|
455
|
+
* },
|
|
456
|
+
* handler: async ({ params, body }) => {
|
|
457
|
+
* const updatedUser = await this.userService.update(params.id, body);
|
|
458
|
+
* return updatedUser;
|
|
459
|
+
* }
|
|
460
|
+
* });
|
|
461
|
+
* }
|
|
462
|
+
* ```
|
|
463
|
+
*
|
|
464
|
+
* @example
|
|
465
|
+
* **File upload with multipart form data:**
|
|
466
|
+
* ```ts
|
|
467
|
+
* class FileController {
|
|
468
|
+
* uploadAvatar = $action({
|
|
469
|
+
* method: "POST",
|
|
470
|
+
* description: "Upload user avatar image",
|
|
471
|
+
* schema: {
|
|
472
|
+
* body: t.object({
|
|
473
|
+
* file: t.file({
|
|
474
|
+
* maxSize: 5 * 1024 * 1024, // 5MB
|
|
475
|
+
* allowedMimeTypes: ["image/jpeg", "image/png", "image/webp"]
|
|
476
|
+
* }),
|
|
477
|
+
* userId: t.string()
|
|
478
|
+
* }),
|
|
479
|
+
* response: t.object({
|
|
480
|
+
* url: t.string({ format: "uri" }),
|
|
481
|
+
* size: t.number(),
|
|
482
|
+
* mimeType: t.string(),
|
|
483
|
+
* uploadedAt: t.datetime()
|
|
484
|
+
* })
|
|
485
|
+
* },
|
|
486
|
+
* handler: async ({ body }) => {
|
|
487
|
+
* const { file, userId } = body;
|
|
488
|
+
*
|
|
489
|
+
* // Validate file
|
|
490
|
+
* await this.fileService.validateImage(file);
|
|
491
|
+
*
|
|
492
|
+
* // Generate unique filename
|
|
493
|
+
* const filename = `avatars/${userId}/${Date.now()}-${file.name}`;
|
|
494
|
+
*
|
|
495
|
+
* // Upload to storage
|
|
496
|
+
* const uploadResult = await this.storageService.upload(filename, file);
|
|
497
|
+
*
|
|
498
|
+
* // Update user profile
|
|
499
|
+
* await this.userService.updateAvatar(userId, uploadResult.url);
|
|
500
|
+
*
|
|
501
|
+
* return {
|
|
502
|
+
* url: uploadResult.url,
|
|
503
|
+
* size: file.size,
|
|
504
|
+
* mimeType: file.type,
|
|
505
|
+
* uploadedAt: new Date().toISOString()
|
|
506
|
+
* };
|
|
507
|
+
* }
|
|
508
|
+
* });
|
|
509
|
+
*
|
|
510
|
+
* downloadFile = $action({
|
|
511
|
+
* method: "GET",
|
|
512
|
+
* description: "Download file by ID",
|
|
513
|
+
* schema: {
|
|
514
|
+
* params: t.object({ id: t.string() }),
|
|
515
|
+
* query: t.object({
|
|
516
|
+
* download: t.optional(t.boolean()),
|
|
517
|
+
* thumbnail: t.optional(t.boolean())
|
|
518
|
+
* }),
|
|
519
|
+
* response: t.file()
|
|
520
|
+
* },
|
|
521
|
+
* handler: async ({ params, query, reply, user }) => {
|
|
522
|
+
* const file = await this.fileService.findById(params.id);
|
|
523
|
+
* if (!file) {
|
|
524
|
+
* throw new Error("File not found");
|
|
525
|
+
* }
|
|
526
|
+
*
|
|
527
|
+
* // Check permissions
|
|
528
|
+
* await this.fileService.checkAccess(params.id, user.id);
|
|
529
|
+
*
|
|
530
|
+
* const fileBuffer = query.thumbnail
|
|
531
|
+
* ? await this.fileService.getThumbnail(file.id)
|
|
532
|
+
* : await this.fileService.getBuffer(file.path);
|
|
533
|
+
*
|
|
534
|
+
* // Set appropriate headers
|
|
535
|
+
* reply.header("Content-Type", file.mimeType);
|
|
536
|
+
* reply.header("Content-Length", fileBuffer.length);
|
|
537
|
+
*
|
|
538
|
+
* if (query.download) {
|
|
539
|
+
* reply.header("Content-Disposition", `attachment; filename="${file.name}"`);
|
|
540
|
+
* }
|
|
541
|
+
*
|
|
542
|
+
* return fileBuffer;
|
|
543
|
+
* }
|
|
544
|
+
* });
|
|
545
|
+
* }
|
|
546
|
+
* ```
|
|
547
|
+
*
|
|
548
|
+
* @example
|
|
549
|
+
* **Advanced API with custom paths and grouped operations:**
|
|
550
|
+
* ```ts
|
|
551
|
+
* class OrderController {
|
|
552
|
+
* group = "orders"; // Groups all actions under "orders" tag
|
|
245
553
|
*
|
|
246
|
-
*
|
|
247
|
-
*
|
|
248
|
-
*
|
|
554
|
+
* // GET /api/orders/search
|
|
555
|
+
* searchOrders = $action({
|
|
556
|
+
* name: "search",
|
|
557
|
+
* path: "/orders/search", // Custom path
|
|
558
|
+
* description: "Advanced order search with filtering",
|
|
559
|
+
* schema: {
|
|
560
|
+
* query: t.object({
|
|
561
|
+
* status: t.optional(t.union([
|
|
562
|
+
* t.literal("pending"),
|
|
563
|
+
* t.literal("processing"),
|
|
564
|
+
* t.literal("shipped"),
|
|
565
|
+
* t.literal("delivered"),
|
|
566
|
+
* t.literal("cancelled")
|
|
567
|
+
* ])),
|
|
568
|
+
* customerId: t.optional(t.string()),
|
|
569
|
+
* dateFrom: t.optional(t.date()),
|
|
570
|
+
* dateTo: t.optional(t.date()),
|
|
571
|
+
* minAmount: t.optional(t.number({ minimum: 0 })),
|
|
572
|
+
* maxAmount: t.optional(t.number({ minimum: 0 })),
|
|
573
|
+
* sortBy: t.optional(t.union([
|
|
574
|
+
* t.literal("createdAt"),
|
|
575
|
+
* t.literal("amount"),
|
|
576
|
+
* t.literal("status")
|
|
577
|
+
* ])),
|
|
578
|
+
* sortOrder: t.optional(t.enum(["asc", "desc"]))
|
|
579
|
+
* }),
|
|
580
|
+
* response: t.object({
|
|
581
|
+
* orders: t.array(t.object({
|
|
582
|
+
* id: t.string(),
|
|
583
|
+
* orderNumber: t.string(),
|
|
584
|
+
* customerId: t.string(),
|
|
585
|
+
* customerName: t.string(),
|
|
586
|
+
* status: t.string(),
|
|
587
|
+
* totalAmount: t.number(),
|
|
588
|
+
* createdAt: t.datetime(),
|
|
589
|
+
* itemCount: t.number()
|
|
590
|
+
* })),
|
|
591
|
+
* pagination: t.object({
|
|
592
|
+
* page: t.number(),
|
|
593
|
+
* limit: t.number(),
|
|
594
|
+
* total: t.number(),
|
|
595
|
+
* hasMore: t.boolean()
|
|
596
|
+
* }),
|
|
597
|
+
* filters: t.object({
|
|
598
|
+
* appliedFilters: t.array(t.string()),
|
|
599
|
+
* availableStatuses: t.array(t.string())
|
|
600
|
+
* })
|
|
601
|
+
* })
|
|
602
|
+
* },
|
|
603
|
+
* handler: async ({ query }) => {
|
|
604
|
+
* // Build dynamic query based on filters
|
|
605
|
+
* const searchCriteria = this.orderService.buildSearchCriteria(query);
|
|
606
|
+
* const results = await this.orderService.searchOrders(searchCriteria);
|
|
607
|
+
*
|
|
608
|
+
* return {
|
|
609
|
+
* orders: results.orders,
|
|
610
|
+
* pagination: results.pagination,
|
|
611
|
+
* filters: {
|
|
612
|
+
* appliedFilters: Object.keys(query).filter(key => query[key] !== undefined),
|
|
613
|
+
* availableStatuses: await this.orderService.getAvailableStatuses()
|
|
614
|
+
* }
|
|
615
|
+
* };
|
|
616
|
+
* }
|
|
617
|
+
* });
|
|
618
|
+
*
|
|
619
|
+
* // POST /api/orders/:id/process
|
|
620
|
+
* processOrder = $action({
|
|
621
|
+
* method: "POST",
|
|
622
|
+
* path: "/orders/:id/process",
|
|
623
|
+
* description: "Process an order through the fulfillment workflow",
|
|
624
|
+
* schema: {
|
|
625
|
+
* params: t.object({ id: t.string() }),
|
|
626
|
+
* body: t.object({
|
|
627
|
+
* notes: t.optional(t.string()),
|
|
628
|
+
* priority: t.optional(t.union([
|
|
629
|
+
* t.literal("low"),
|
|
630
|
+
* t.literal("normal"),
|
|
631
|
+
* t.literal("high"),
|
|
632
|
+
* t.literal("urgent")
|
|
633
|
+
* ])),
|
|
634
|
+
* assignToWarehouse: t.optional(t.string())
|
|
635
|
+
* }),
|
|
636
|
+
* response: t.object({
|
|
637
|
+
* orderId: t.string(),
|
|
638
|
+
* status: t.string(),
|
|
639
|
+
* processedAt: t.datetime(),
|
|
640
|
+
* estimatedFulfillment: t.datetime(),
|
|
641
|
+
* trackingInfo: t.optional(t.object({
|
|
642
|
+
* trackingNumber: t.string(),
|
|
643
|
+
* carrier: t.string(),
|
|
644
|
+
* estimatedDelivery: t.date()
|
|
645
|
+
* }))
|
|
646
|
+
* })
|
|
647
|
+
* },
|
|
648
|
+
* handler: async ({ params, body, user }) => {
|
|
649
|
+
* // Validate order can be processed
|
|
650
|
+
* const order = await this.orderService.findById(params.id);
|
|
651
|
+
* if (!order || order.status !== "pending") {
|
|
652
|
+
* throw new Error("Order cannot be processed in current status");
|
|
653
|
+
* }
|
|
654
|
+
*
|
|
655
|
+
* // Check inventory availability
|
|
656
|
+
* const inventoryCheck = await this.inventoryService.checkAvailability(order.items);
|
|
657
|
+
* if (!inventoryCheck.available) {
|
|
658
|
+
* throw new Error(`Insufficient inventory: ${inventoryCheck.missingItems.join(", ")}`);
|
|
659
|
+
* }
|
|
660
|
+
*
|
|
661
|
+
* // Process the order
|
|
662
|
+
* const processResult = await this.fulfillmentService.processOrder({
|
|
663
|
+
* orderId: params.id,
|
|
664
|
+
* options: {
|
|
665
|
+
* notes: body.notes,
|
|
666
|
+
* priority: body.priority || "normal",
|
|
667
|
+
* warehouse: body.assignToWarehouse
|
|
668
|
+
* }
|
|
669
|
+
* });
|
|
670
|
+
*
|
|
671
|
+
* // Update order status
|
|
672
|
+
* await this.orderService.updateStatus(params.id, "processing", {
|
|
673
|
+
* processedBy: user.id,
|
|
674
|
+
* processedAt: new Date(),
|
|
675
|
+
* notes: body.notes
|
|
676
|
+
* });
|
|
677
|
+
*
|
|
678
|
+
* // Send notification
|
|
679
|
+
* await this.notificationService.sendOrderUpdate(order.customerId, {
|
|
680
|
+
* orderId: params.id,
|
|
681
|
+
* status: "processing",
|
|
682
|
+
* message: "Your order is now being processed"
|
|
683
|
+
* });
|
|
684
|
+
*
|
|
685
|
+
* return {
|
|
686
|
+
* orderId: params.id,
|
|
687
|
+
* status: "processing",
|
|
688
|
+
* processedAt: new Date().toISOString(),
|
|
689
|
+
* estimatedFulfillment: processResult.estimatedCompletion,
|
|
690
|
+
* trackingInfo: processResult.trackingInfo
|
|
691
|
+
* };
|
|
692
|
+
* }
|
|
693
|
+
* });
|
|
694
|
+
* }
|
|
695
|
+
* ```
|
|
249
696
|
*
|
|
250
697
|
* @example
|
|
698
|
+
* **Actions with security integration and role-based access:**
|
|
251
699
|
* ```ts
|
|
252
|
-
* class
|
|
253
|
-
*
|
|
254
|
-
*
|
|
255
|
-
*
|
|
700
|
+
* class AdminController {
|
|
701
|
+
* group = "admin";
|
|
702
|
+
*
|
|
703
|
+
* // Only accessible to users with "admin:users:read" permission
|
|
704
|
+
* getUserStats = $action({
|
|
705
|
+
* description: "Get comprehensive user statistics",
|
|
706
|
+
* security: { permissions: ["admin:users:read"] },
|
|
707
|
+
* schema: {
|
|
708
|
+
* query: t.object({
|
|
709
|
+
* includeInactive: t.optional(t.boolean())
|
|
710
|
+
* }),
|
|
711
|
+
* response: t.object({
|
|
712
|
+
* totalUsers: t.number(),
|
|
713
|
+
* activeUsers: t.number(),
|
|
714
|
+
* newUsers: t.number(),
|
|
715
|
+
* userGrowth: t.number(),
|
|
716
|
+
* breakdown: t.object({
|
|
717
|
+
* byRole: t.record(t.string(), t.number()),
|
|
718
|
+
* byStatus: t.record(t.string(), t.number()),
|
|
719
|
+
* byRegistrationSource: t.record(t.string(), t.number())
|
|
720
|
+
* }),
|
|
721
|
+
* trends: t.array(t.object({
|
|
722
|
+
* date: t.date(),
|
|
723
|
+
* registrations: t.number(),
|
|
724
|
+
* activations: t.number()
|
|
725
|
+
* }))
|
|
726
|
+
* })
|
|
727
|
+
* },
|
|
728
|
+
* handler: async ({ query, user }) => {
|
|
729
|
+
* // user is available through security integration
|
|
730
|
+
* this.auditLogger.log({
|
|
731
|
+
* action: "admin.getUserStats",
|
|
732
|
+
* userId: user.id,
|
|
733
|
+
* userRole: user.role,
|
|
734
|
+
* timestamp: new Date()
|
|
735
|
+
* });
|
|
736
|
+
*
|
|
737
|
+
* const period = query.period || "month";
|
|
738
|
+
* const stats = await this.analyticsService.getUserStatistics({
|
|
739
|
+
* period,
|
|
740
|
+
* includeInactive: query.includeInactive || false
|
|
741
|
+
* });
|
|
742
|
+
*
|
|
743
|
+
* return stats;
|
|
744
|
+
* }
|
|
745
|
+
* });
|
|
746
|
+
*
|
|
747
|
+
* // Bulk operations with transaction support
|
|
748
|
+
* bulkUpdateUsers = $action({
|
|
749
|
+
* method: "POST",
|
|
750
|
+
* path: "/admin/users/bulk-update",
|
|
751
|
+
* description: "Bulk update user properties",
|
|
752
|
+
* security: { permissions: ["admin:users:write"] },
|
|
753
|
+
* schema: {
|
|
754
|
+
* body: t.object({
|
|
755
|
+
* userIds: t.array(t.string(), { minItems: 1, maxItems: 1000 }),
|
|
756
|
+
* updates: t.object({
|
|
757
|
+
* status: t.optional(t.union([t.literal("active"), t.literal("inactive")])),
|
|
758
|
+
* role: t.optional(t.string()),
|
|
759
|
+
* tags: t.optional(t.array(t.string())),
|
|
760
|
+
* customFields: t.optional(t.record(t.string(), t.any()))
|
|
761
|
+
* }),
|
|
762
|
+
* reason: t.string({ minLength: 10, maxLength: 500 })
|
|
763
|
+
* }),
|
|
764
|
+
* response: t.object({
|
|
765
|
+
* updated: t.number(),
|
|
766
|
+
* failed: t.number(),
|
|
767
|
+
* errors: t.array(t.object({
|
|
768
|
+
* userId: t.string(),
|
|
769
|
+
* error: t.string()
|
|
770
|
+
* })),
|
|
771
|
+
* auditLogId: t.string()
|
|
772
|
+
* })
|
|
773
|
+
* },
|
|
774
|
+
* handler: async ({ body, user }) => {
|
|
775
|
+
* const results = { updated: 0, failed: 0, errors: [] };
|
|
776
|
+
*
|
|
777
|
+
* // Create audit log entry
|
|
778
|
+
* const auditLogId = await this.auditService.logBulkOperation({
|
|
779
|
+
* operation: "bulk_user_update",
|
|
780
|
+
* initiatedBy: user.id,
|
|
781
|
+
* targetCount: body.userIds.length,
|
|
782
|
+
* reason: body.reason,
|
|
783
|
+
* changes: body.updates
|
|
784
|
+
* });
|
|
785
|
+
*
|
|
786
|
+
* // Process in batches to avoid overwhelming the database
|
|
787
|
+
* const batchSize = 50;
|
|
788
|
+
* for (let i = 0; i < body.userIds.length; i += batchSize) {
|
|
789
|
+
* const batch = body.userIds.slice(i, i + batchSize);
|
|
790
|
+
*
|
|
791
|
+
* try {
|
|
792
|
+
* const updateResult = await this.userService.bulkUpdate(batch, body.updates);
|
|
793
|
+
* results.updated += updateResult.success;
|
|
794
|
+
* results.failed += updateResult.failed;
|
|
795
|
+
* results.errors.push(...updateResult.errors);
|
|
796
|
+
* } catch (error) {
|
|
797
|
+
* // Log batch failure but continue processing
|
|
798
|
+
* this.logger.error(`Bulk update batch failed`, {
|
|
799
|
+
* batch: i / batchSize + 1,
|
|
800
|
+
* userIds: batch,
|
|
801
|
+
* error: error.message
|
|
802
|
+
* });
|
|
803
|
+
*
|
|
804
|
+
* results.failed += batch.length;
|
|
805
|
+
* results.errors.push(...batch.map(userId => ({
|
|
806
|
+
* userId,
|
|
807
|
+
* error: error.message
|
|
808
|
+
* })));
|
|
809
|
+
* }
|
|
810
|
+
* }
|
|
811
|
+
*
|
|
812
|
+
* // Update audit log with results
|
|
813
|
+
* await this.auditService.updateBulkOperationResults(auditLogId, results);
|
|
814
|
+
*
|
|
815
|
+
* return { ...results, auditLogId };
|
|
816
|
+
* }
|
|
817
|
+
* });
|
|
256
818
|
* }
|
|
257
|
-
* // GET /api/hello -> "Hello World"
|
|
258
819
|
* ```
|
|
820
|
+
*
|
|
821
|
+
* **Important Notes**:
|
|
822
|
+
* - Actions are automatically registered with the HTTP server when the service is initialized
|
|
823
|
+
* - Use `run()` for direct invocation (testing, internal calls, or remote services)
|
|
824
|
+
* - Use `fetch()` for explicit HTTP requests (client-side, external services)
|
|
825
|
+
* - Schema validation occurs automatically for all requests and responses
|
|
826
|
+
* - Path parameters are automatically extracted from schema definitions
|
|
827
|
+
* - Content-Type headers are automatically set based on schema types
|
|
828
|
+
* - Actions can be disabled via the `disabled` option for maintenance or feature flags
|
|
829
|
+
*
|
|
830
|
+
* @stability 2
|
|
259
831
|
*/
|
|
260
832
|
declare const $action: {
|
|
261
833
|
<TConfig extends RequestConfigSchema>(options: ActionDescriptorOptions<TConfig>): ActionDescriptor<TConfig>;
|
|
@@ -491,22 +1063,22 @@ declare const isMultipart: (options: {
|
|
|
491
1063
|
}) => boolean;
|
|
492
1064
|
//#endregion
|
|
493
1065
|
//#region src/schemas/errorSchema.d.ts
|
|
494
|
-
declare const errorSchema:
|
|
495
|
-
error:
|
|
496
|
-
status:
|
|
497
|
-
message:
|
|
498
|
-
details:
|
|
499
|
-
cause:
|
|
500
|
-
name:
|
|
501
|
-
message:
|
|
1066
|
+
declare const errorSchema: _sinclair_typebox0.TObject<{
|
|
1067
|
+
error: _sinclair_typebox0.TString;
|
|
1068
|
+
status: _sinclair_typebox0.TNumber;
|
|
1069
|
+
message: _sinclair_typebox0.TString;
|
|
1070
|
+
details: _sinclair_typebox0.TOptional<_sinclair_typebox0.TString>;
|
|
1071
|
+
cause: _sinclair_typebox0.TOptional<_sinclair_typebox0.TObject<{
|
|
1072
|
+
name: _sinclair_typebox0.TString;
|
|
1073
|
+
message: _sinclair_typebox0.TString;
|
|
502
1074
|
}>>;
|
|
503
1075
|
}>;
|
|
504
1076
|
//#endregion
|
|
505
1077
|
//#region src/schemas/okSchema.d.ts
|
|
506
|
-
declare const okSchema:
|
|
507
|
-
ok:
|
|
508
|
-
id:
|
|
509
|
-
count:
|
|
1078
|
+
declare const okSchema: _sinclair_typebox0.TObject<{
|
|
1079
|
+
ok: _sinclair_typebox0.TBoolean;
|
|
1080
|
+
id: _sinclair_typebox0.TOptional<_sinclair_typebox0.TUnion<[_sinclair_typebox0.TString, _sinclair_typebox0.TInteger]>>;
|
|
1081
|
+
count: _sinclair_typebox0.TOptional<_sinclair_typebox0.TNumber>;
|
|
510
1082
|
}>;
|
|
511
1083
|
type Ok = Static<typeof okSchema>;
|
|
512
1084
|
//#endregion
|
|
@@ -628,5 +1200,5 @@ declare module "alepha" {
|
|
|
628
1200
|
*/
|
|
629
1201
|
declare const AlephaServer: _alepha_core1.Service<_alepha_core1.Module>;
|
|
630
1202
|
//#endregion
|
|
631
|
-
export { $action, $route, ActionDescriptor, ActionDescriptorOptions, AlephaServer, BadRequestError, ClientRequestEntry, ClientRequestEntryContainer, ClientRequestOptions, ClientRequestResponse, ConflictError, FetchActionArgs, FetchOptions, FetchResponse, ForbiddenError, HttpAction, HttpClient, HttpClientPendingRequests, HttpError, HttpErrorLike, NodeHttpServerProvider, NotFoundError, Ok, RequestConfigSchema, ResponseBodyType, ResponseKind, RouteDescriptor, RouteDescriptorOptions, RouteMethod, ServerActionHandler, ServerActionRequest, ServerHandler, ServerLoggerProvider, ServerMiddlewareHandler, ServerNotReadyProvider, ServerProvider, ServerRawRequest, ServerReply, ServerRequest, ServerRequestConfig, ServerRequestConfigEntry, ServerResponse, ServerResponseBody, ServerRoute, ServerRouteMatcher, ServerRouterProvider, ServerTimingProvider, UnauthorizedError, ValidationError, errorNameByStatus, errorSchema, isHttpError, isMultipart, okSchema, routeMethods };
|
|
1203
|
+
export { $action, $route, ActionDescriptor, ActionDescriptorOptions, AlephaServer, BadRequestError, ClientRequestEntry, ClientRequestEntryContainer, ClientRequestOptions, ClientRequestResponse, ConflictError, FetchActionArgs, FetchOptions, FetchResponse, ForbiddenError, HttpAction, HttpClient, HttpClientPendingRequests, HttpError, HttpErrorLike, NodeHttpServerProvider, NotFoundError, Ok, RequestConfigSchema, ResponseBodyType, ResponseKind, RouteDescriptor, RouteDescriptorOptions, RouteMethod, ServerActionHandler, ServerActionRequest, ServerHandler, ServerLoggerProvider, ServerMiddlewareHandler, ServerNotReadyProvider, ServerProvider, ServerRawRequest, ServerReply, ServerRequest, ServerRequestConfig, ServerRequestConfigEntry, ServerResponse, ServerResponseBody, ServerRoute, ServerRouteMatcher, ServerRouteRequestHandler, ServerRouterProvider, ServerTimingProvider, UnauthorizedError, ValidationError, errorNameByStatus, errorSchema, isHttpError, isMultipart, okSchema, routeMethods };
|
|
632
1204
|
//# sourceMappingURL=index.d.ts.map
|