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/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 _sinclair_typebox7 from "@sinclair/typebox";
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
- setStatus(status: number): void;
26
- setHeader(name: string, value: string): void;
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: (request: ServerRawRequest) => Promise<ServerResponse>;
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 userAgentParser: UserAgentParser;
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
- * Create an action endpoint.
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
- * By default, all actions are prefixed by `/api`.
247
- * If `name` is not provided, the action will be named after the property key.
248
- * If `path` is not provided, the action will be named after the function name.
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 MyController {
253
- * hello = $action({
254
- * handler: () => "Hello World",
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: _sinclair_typebox7.TObject<{
495
- error: _sinclair_typebox7.TString;
496
- status: _sinclair_typebox7.TNumber;
497
- message: _sinclair_typebox7.TString;
498
- details: _sinclair_typebox7.TOptional<_sinclair_typebox7.TString>;
499
- cause: _sinclair_typebox7.TOptional<_sinclair_typebox7.TObject<{
500
- name: _sinclair_typebox7.TString;
501
- message: _sinclair_typebox7.TString;
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: _sinclair_typebox7.TObject<{
507
- ok: _sinclair_typebox7.TBoolean;
508
- id: _sinclair_typebox7.TOptional<_sinclair_typebox7.TUnion<[_sinclair_typebox7.TString, _sinclair_typebox7.TInteger]>>;
509
- count: _sinclair_typebox7.TOptional<_sinclair_typebox7.TNumber>;
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