@cosmneo/onion-lasagna 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/dist/backend/core/global.cjs +283 -0
- package/dist/backend/core/global.cjs.map +1 -0
- package/dist/backend/core/global.d.cts +294 -0
- package/dist/backend/core/global.d.ts +294 -0
- package/dist/backend/core/global.js +39 -0
- package/dist/backend/core/global.js.map +1 -0
- package/dist/backend/core/onion-layers.cjs +2302 -0
- package/dist/backend/core/onion-layers.cjs.map +1 -0
- package/dist/backend/core/onion-layers.d.cts +1675 -0
- package/dist/backend/core/onion-layers.d.ts +1675 -0
- package/dist/backend/core/onion-layers.js +1158 -0
- package/dist/backend/core/onion-layers.js.map +1 -0
- package/dist/backend/core/presentation.cjs +573 -0
- package/dist/backend/core/presentation.cjs.map +1 -0
- package/dist/backend/core/presentation.d.cts +5 -0
- package/dist/backend/core/presentation.d.ts +5 -0
- package/dist/backend/core/presentation.js +28 -0
- package/dist/backend/core/presentation.js.map +1 -0
- package/dist/backend/core/validators/arktype.cjs +947 -0
- package/dist/backend/core/validators/arktype.cjs.map +1 -0
- package/dist/backend/core/validators/arktype.d.cts +188 -0
- package/dist/backend/core/validators/arktype.d.ts +188 -0
- package/dist/backend/core/validators/arktype.js +287 -0
- package/dist/backend/core/validators/arktype.js.map +1 -0
- package/dist/backend/core/validators/typebox.cjs +939 -0
- package/dist/backend/core/validators/typebox.cjs.map +1 -0
- package/dist/backend/core/validators/typebox.d.cts +189 -0
- package/dist/backend/core/validators/typebox.d.ts +189 -0
- package/dist/backend/core/validators/typebox.js +281 -0
- package/dist/backend/core/validators/typebox.js.map +1 -0
- package/dist/backend/core/validators/valibot.cjs +942 -0
- package/dist/backend/core/validators/valibot.cjs.map +1 -0
- package/dist/backend/core/validators/valibot.d.cts +160 -0
- package/dist/backend/core/validators/valibot.d.ts +160 -0
- package/dist/backend/core/validators/valibot.js +294 -0
- package/dist/backend/core/validators/valibot.js.map +1 -0
- package/dist/backend/core/validators/zod.cjs +934 -0
- package/dist/backend/core/validators/zod.cjs.map +1 -0
- package/dist/backend/core/validators/zod.d.cts +188 -0
- package/dist/backend/core/validators/zod.d.ts +188 -0
- package/dist/backend/core/validators/zod.js +278 -0
- package/dist/backend/core/validators/zod.js.map +1 -0
- package/dist/backend/frameworks/elysia.cjs +715 -0
- package/dist/backend/frameworks/elysia.cjs.map +1 -0
- package/dist/backend/frameworks/elysia.d.cts +208 -0
- package/dist/backend/frameworks/elysia.d.ts +208 -0
- package/dist/backend/frameworks/elysia.js +251 -0
- package/dist/backend/frameworks/elysia.js.map +1 -0
- package/dist/backend/frameworks/fastify.cjs +677 -0
- package/dist/backend/frameworks/fastify.cjs.map +1 -0
- package/dist/backend/frameworks/fastify.d.cts +201 -0
- package/dist/backend/frameworks/fastify.d.ts +201 -0
- package/dist/backend/frameworks/fastify.js +213 -0
- package/dist/backend/frameworks/fastify.js.map +1 -0
- package/dist/backend/frameworks/hono.cjs +715 -0
- package/dist/backend/frameworks/hono.cjs.map +1 -0
- package/dist/backend/frameworks/hono.d.cts +163 -0
- package/dist/backend/frameworks/hono.d.ts +163 -0
- package/dist/backend/frameworks/hono.js +249 -0
- package/dist/backend/frameworks/hono.js.map +1 -0
- package/dist/backend/frameworks/nestjs.cjs +260 -0
- package/dist/backend/frameworks/nestjs.cjs.map +1 -0
- package/dist/backend/frameworks/nestjs.d.cts +168 -0
- package/dist/backend/frameworks/nestjs.d.ts +168 -0
- package/dist/backend/frameworks/nestjs.js +193 -0
- package/dist/backend/frameworks/nestjs.js.map +1 -0
- package/dist/base-dto.class-D7W9iqoU.d.cts +146 -0
- package/dist/base-dto.class-D7W9iqoU.d.ts +146 -0
- package/dist/base-uuid-v7.vo-BPGEIWLM.d.ts +799 -0
- package/dist/base-uuid-v7.vo-BjqKX44G.d.cts +799 -0
- package/dist/chunk-74IKUOSE.js +116 -0
- package/dist/chunk-74IKUOSE.js.map +1 -0
- package/dist/chunk-BKZOLGQW.js +29 -0
- package/dist/chunk-BKZOLGQW.js.map +1 -0
- package/dist/chunk-CGZBV6BD.js +54 -0
- package/dist/chunk-CGZBV6BD.js.map +1 -0
- package/dist/chunk-DDAHJZVK.js +258 -0
- package/dist/chunk-DDAHJZVK.js.map +1 -0
- package/dist/chunk-MQD5GXMT.js +171 -0
- package/dist/chunk-MQD5GXMT.js.map +1 -0
- package/dist/chunk-OKFXZHBC.js +43 -0
- package/dist/chunk-OKFXZHBC.js.map +1 -0
- package/dist/chunk-RLLWYFPI.js +168 -0
- package/dist/chunk-RLLWYFPI.js.map +1 -0
- package/dist/chunk-VCHFXT5W.js +425 -0
- package/dist/chunk-VCHFXT5W.js.map +1 -0
- package/dist/chunk-ZWLYNGO3.js +40 -0
- package/dist/chunk-ZWLYNGO3.js.map +1 -0
- package/dist/http-response-BAhi8lF4.d.cts +124 -0
- package/dist/http-response-BAhi8lF4.d.ts +124 -0
- package/dist/index-DingXh7B.d.cts +1187 -0
- package/dist/index-tOH7XBa3.d.ts +1187 -0
- package/dist/routing.type-DB4pt-d9.d.ts +184 -0
- package/dist/routing.type-DF2BIL7x.d.cts +184 -0
- package/dist/validation-error.type-kD4_qNZ9.d.cts +199 -0
- package/dist/validation-error.type-kD4_qNZ9.d.ts +199 -0
- package/package.json +191 -0
|
@@ -0,0 +1,1187 @@
|
|
|
1
|
+
import { B as BaseDto } from './base-dto.class-D7W9iqoU.js';
|
|
2
|
+
import { C as CodedError, P as PresentationErrorCode, V as ValidationError } from './validation-error.type-kD4_qNZ9.js';
|
|
3
|
+
import { H as HttpRequest, a as HttpResponse } from './http-response-BAhi8lF4.js';
|
|
4
|
+
import './routing.type-DB4pt-d9.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Primary port interface for use case execution (Hexagonal Architecture).
|
|
8
|
+
*
|
|
9
|
+
* Defines the contract for inbound adapters that handle application use cases.
|
|
10
|
+
* Implementations receive a validated input DTO and return an output DTO.
|
|
11
|
+
*
|
|
12
|
+
* @typeParam TInDto - Input DTO type, must extend BaseDto
|
|
13
|
+
* @typeParam TOutDto - Output DTO type, must extend BaseDto
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```typescript
|
|
17
|
+
* interface CreateUserPort extends BaseInboundPort<CreateUserInputDto, CreateUserOutputDto> {}
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
interface BaseInboundPort<TInDto extends BaseDto<unknown>, TOutDto extends BaseDto<unknown>> {
|
|
21
|
+
/**
|
|
22
|
+
* Executes the use case with the provided input.
|
|
23
|
+
*
|
|
24
|
+
* @param input - Validated input DTO
|
|
25
|
+
* @returns Promise resolving to the output DTO
|
|
26
|
+
* @throws {UseCaseError} When use case execution fails
|
|
27
|
+
* @throws {DomainError} When domain invariants are violated
|
|
28
|
+
* @throws {InfraError} When infrastructure operations fail
|
|
29
|
+
*/
|
|
30
|
+
execute(input: TInDto): Promise<TOutDto>;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Configuration for creating a BaseController instance.
|
|
35
|
+
*
|
|
36
|
+
* All types must be DTOs (extending BaseDto) to ensure validation at every boundary:
|
|
37
|
+
* - TRequestDto: Validated HTTP request (body, headers, params, query)
|
|
38
|
+
* - TResponseDto: Validated HTTP response structure
|
|
39
|
+
* - TInDto: Validated use case input
|
|
40
|
+
* - TOutDto: Validated use case output
|
|
41
|
+
*
|
|
42
|
+
* @typeParam TRequestDto - Validated request DTO from the framework layer
|
|
43
|
+
* @typeParam TResponseDto - Validated response DTO to return to the framework
|
|
44
|
+
* @typeParam TInDto - Input DTO type for the use case
|
|
45
|
+
* @typeParam TOutDto - Output DTO type from the use case
|
|
46
|
+
*/
|
|
47
|
+
interface BaseControllerConfig<TRequestDto extends BaseDto<unknown>, TResponseDto extends BaseDto<unknown>, TInDto extends BaseDto<unknown>, TOutDto extends BaseDto<unknown>> {
|
|
48
|
+
/** Maps the validated request DTO to a use case input DTO. */
|
|
49
|
+
requestMapper: (request: TRequestDto) => TInDto;
|
|
50
|
+
/** The use case to execute. */
|
|
51
|
+
useCase: BaseInboundPort<TInDto, TOutDto>;
|
|
52
|
+
/** Maps the use case output DTO to a validated response DTO. */
|
|
53
|
+
responseMapper: (output: TOutDto) => TResponseDto;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Base controller implementing the request/response pipeline.
|
|
57
|
+
*
|
|
58
|
+
* Orchestrates the flow: `requestDto → mapRequest → executeUseCase → mapResponse → responseDto`
|
|
59
|
+
*
|
|
60
|
+
* All boundaries are validated DTOs:
|
|
61
|
+
* - Input: TRequestDto (validated by framework layer before controller)
|
|
62
|
+
* - Use case input: TInDto (validated by mapRequest)
|
|
63
|
+
* - Use case output: TOutDto (validated by use case)
|
|
64
|
+
* - Output: TResponseDto (validated by mapResponse)
|
|
65
|
+
*
|
|
66
|
+
* Features:
|
|
67
|
+
* - Converts {@link ObjectValidationError} to {@link InvalidRequestError}
|
|
68
|
+
* - Passes through known {@link CodedError} types
|
|
69
|
+
* - Wraps unknown errors in {@link ControllerError}
|
|
70
|
+
*
|
|
71
|
+
* Architecture:
|
|
72
|
+
* - {@link execute} - Public entry point with error wrapping (do not override)
|
|
73
|
+
* - {@link pipeline} - Protected pipeline orchestration (override for custom flow)
|
|
74
|
+
* - {@link mapRequest} - Request-to-input transformation
|
|
75
|
+
* - {@link executeUseCase} - Use case execution
|
|
76
|
+
* - {@link mapResponse} - Output-to-response transformation
|
|
77
|
+
*
|
|
78
|
+
* @typeParam TRequestDto - Validated request DTO from the framework layer
|
|
79
|
+
* @typeParam TResponseDto - Validated response DTO to return to the framework
|
|
80
|
+
* @typeParam TInDto - Input DTO type for the use case
|
|
81
|
+
* @typeParam TOutDto - Output DTO type from the use case
|
|
82
|
+
*
|
|
83
|
+
* @example Basic usage
|
|
84
|
+
* ```typescript
|
|
85
|
+
* const controller = BaseController.create({
|
|
86
|
+
* requestMapper: (req) => CreateUserInputDto.create(req.data),
|
|
87
|
+
* useCase: createUserUseCase,
|
|
88
|
+
* responseMapper: (output) => CreateUserResponseDto.create({ id: output.data.id }),
|
|
89
|
+
* });
|
|
90
|
+
*
|
|
91
|
+
* // Framework layer creates the request DTO
|
|
92
|
+
* const requestDto = CreateUserRequestDto.create(httpRequest);
|
|
93
|
+
* const responseDto = await controller.execute(requestDto);
|
|
94
|
+
* ```
|
|
95
|
+
*
|
|
96
|
+
* @example Custom controller overriding pipeline
|
|
97
|
+
* ```typescript
|
|
98
|
+
* class LoggingController extends BaseController<...> {
|
|
99
|
+
* protected override async pipeline(input: TRequestDto): Promise<TResponseDto> {
|
|
100
|
+
* console.log('Request received:', input);
|
|
101
|
+
* const result = await super.pipeline(input);
|
|
102
|
+
* console.log('Response:', result);
|
|
103
|
+
* return result;
|
|
104
|
+
* }
|
|
105
|
+
* }
|
|
106
|
+
* ```
|
|
107
|
+
*
|
|
108
|
+
* @example Custom controller overriding individual methods
|
|
109
|
+
* ```typescript
|
|
110
|
+
* class CachingController extends BaseController<...> {
|
|
111
|
+
* private cache = new Map();
|
|
112
|
+
*
|
|
113
|
+
* protected override async executeUseCase(input: TInDto): Promise<TOutDto> {
|
|
114
|
+
* const key = JSON.stringify(input.data);
|
|
115
|
+
* if (this.cache.has(key)) return this.cache.get(key);
|
|
116
|
+
* const result = await super.executeUseCase(input);
|
|
117
|
+
* this.cache.set(key, result);
|
|
118
|
+
* return result;
|
|
119
|
+
* }
|
|
120
|
+
* }
|
|
121
|
+
* ```
|
|
122
|
+
*/
|
|
123
|
+
declare class BaseController<TRequestDto extends BaseDto<unknown>, TResponseDto extends BaseDto<unknown>, TInDto extends BaseDto<unknown>, TOutDto extends BaseDto<unknown>> {
|
|
124
|
+
protected readonly requestMapper: (request: TRequestDto) => TInDto;
|
|
125
|
+
protected readonly useCase: BaseInboundPort<TInDto, TOutDto>;
|
|
126
|
+
protected readonly responseMapper: (output: TOutDto) => TResponseDto;
|
|
127
|
+
/**
|
|
128
|
+
* Creates a new BaseController instance.
|
|
129
|
+
*
|
|
130
|
+
* @param requestMapper - Function to map request DTO to use case input DTO
|
|
131
|
+
* @param useCase - The use case port to execute
|
|
132
|
+
* @param responseMapper - Function to map use case output DTO to response DTO
|
|
133
|
+
*/
|
|
134
|
+
constructor(requestMapper: (request: TRequestDto) => TInDto, useCase: BaseInboundPort<TInDto, TOutDto>, responseMapper: (output: TOutDto) => TResponseDto);
|
|
135
|
+
/**
|
|
136
|
+
* Factory method to create a controller from a configuration object.
|
|
137
|
+
*
|
|
138
|
+
* @param config - Controller configuration
|
|
139
|
+
* @returns A new BaseController instance
|
|
140
|
+
*/
|
|
141
|
+
static create<TRequestDto extends BaseDto<unknown>, TResponseDto extends BaseDto<unknown>, TInDto extends BaseDto<unknown>, TOutDto extends BaseDto<unknown>>(config: BaseControllerConfig<TRequestDto, TResponseDto, TInDto, TOutDto>): BaseController<TRequestDto, TResponseDto, TInDto, TOutDto>;
|
|
142
|
+
/**
|
|
143
|
+
* Executes the controller pipeline with error wrapping.
|
|
144
|
+
*
|
|
145
|
+
* This is the public entry point that ensures consistent error handling.
|
|
146
|
+
* All errors are wrapped in {@link ControllerError} unless they extend {@link CodedError}.
|
|
147
|
+
*
|
|
148
|
+
* **Do not override this method.** Override {@link pipeline} instead for custom pipeline logic.
|
|
149
|
+
*
|
|
150
|
+
* @param input - The validated request DTO
|
|
151
|
+
* @returns Promise resolving to the validated response DTO
|
|
152
|
+
* @throws {InvalidRequestError} When request mapping/validation fails
|
|
153
|
+
* @throws {ControllerError} When an unexpected error occurs
|
|
154
|
+
* @throws {CodedError} When use case throws a known error type
|
|
155
|
+
*/
|
|
156
|
+
execute(input: TRequestDto): Promise<TResponseDto>;
|
|
157
|
+
/**
|
|
158
|
+
* Runs the controller pipeline.
|
|
159
|
+
*
|
|
160
|
+
* Orchestrates: `mapRequest → executeUseCase → mapResponse`
|
|
161
|
+
*
|
|
162
|
+
* Override this method to customize the entire pipeline flow, or override
|
|
163
|
+
* individual protected methods ({@link mapRequest}, {@link executeUseCase},
|
|
164
|
+
* {@link mapResponse}) for more granular control.
|
|
165
|
+
*
|
|
166
|
+
* @param input - The validated request DTO
|
|
167
|
+
* @returns Promise resolving to the validated response DTO
|
|
168
|
+
*/
|
|
169
|
+
protected pipeline(input: TRequestDto): Promise<TResponseDto>;
|
|
170
|
+
/**
|
|
171
|
+
* Maps the request DTO to a use case input DTO.
|
|
172
|
+
*
|
|
173
|
+
* Override to add custom pre-processing, logging, or transformation logic.
|
|
174
|
+
* The default implementation uses the configured `requestMapper` and converts
|
|
175
|
+
* {@link ObjectValidationError} to {@link InvalidRequestError}.
|
|
176
|
+
*
|
|
177
|
+
* @param input - The validated request DTO
|
|
178
|
+
* @returns The use case input DTO
|
|
179
|
+
* @throws {InvalidRequestError} When validation fails
|
|
180
|
+
* @throws {ControllerError} When mapping fails unexpectedly
|
|
181
|
+
*/
|
|
182
|
+
protected mapRequest(input: TRequestDto): TInDto;
|
|
183
|
+
/**
|
|
184
|
+
* Executes the use case with the mapped input.
|
|
185
|
+
*
|
|
186
|
+
* Override to add custom logic around use case execution, such as:
|
|
187
|
+
* - Logging/tracing
|
|
188
|
+
* - Caching
|
|
189
|
+
* - Retry logic
|
|
190
|
+
* - Multi-use-case orchestration
|
|
191
|
+
*
|
|
192
|
+
* @param input - The use case input DTO
|
|
193
|
+
* @returns Promise resolving to the use case output DTO
|
|
194
|
+
*/
|
|
195
|
+
protected executeUseCase(input: TInDto): Promise<TOutDto>;
|
|
196
|
+
/**
|
|
197
|
+
* Maps the use case output DTO to a response DTO.
|
|
198
|
+
*
|
|
199
|
+
* Override to add custom post-processing, logging, or transformation logic.
|
|
200
|
+
* The default implementation uses the configured `responseMapper`.
|
|
201
|
+
*
|
|
202
|
+
* @param output - The use case output DTO
|
|
203
|
+
* @returns The response DTO
|
|
204
|
+
* @throws {ControllerError} When mapping or validation fails
|
|
205
|
+
*/
|
|
206
|
+
protected mapResponse(output: TOutDto): TResponseDto;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Result returned by an {@link AccessGuard} function.
|
|
211
|
+
*
|
|
212
|
+
* Represents the outcome of an authorization check, indicating whether
|
|
213
|
+
* access should be granted and optionally providing a reason for denial.
|
|
214
|
+
*
|
|
215
|
+
* **Usage with GuardedController:**
|
|
216
|
+
* - If `isAllowed: true`, the controller method executes normally
|
|
217
|
+
* - If `isAllowed: false`, throws {@link AccessDeniedError} with the reason
|
|
218
|
+
*
|
|
219
|
+
* @example Allowing access
|
|
220
|
+
* ```typescript
|
|
221
|
+
* const result: AccessGuardResult = {
|
|
222
|
+
* isAllowed: true,
|
|
223
|
+
* };
|
|
224
|
+
* ```
|
|
225
|
+
*
|
|
226
|
+
* @example Denying access with reason
|
|
227
|
+
* ```typescript
|
|
228
|
+
* const result: AccessGuardResult = {
|
|
229
|
+
* isAllowed: false,
|
|
230
|
+
* reason: 'User does not have admin privileges',
|
|
231
|
+
* };
|
|
232
|
+
* ```
|
|
233
|
+
*/
|
|
234
|
+
interface AccessGuardResult {
|
|
235
|
+
/**
|
|
236
|
+
* Whether the request should be allowed to proceed.
|
|
237
|
+
*
|
|
238
|
+
* - `true`: Access granted, method executes
|
|
239
|
+
* - `false`: Access denied, throws {@link AccessDeniedError}
|
|
240
|
+
*/
|
|
241
|
+
isAllowed: boolean;
|
|
242
|
+
/**
|
|
243
|
+
* Optional explanation for why access was denied.
|
|
244
|
+
*
|
|
245
|
+
* When `isAllowed` is `false`, this message is passed to
|
|
246
|
+
* {@link AccessDeniedError} and can be returned to the client.
|
|
247
|
+
* Defaults to "Access denied" if not provided.
|
|
248
|
+
*/
|
|
249
|
+
reason?: string;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Access guard types for controller authorization.
|
|
254
|
+
*
|
|
255
|
+
* Provides type definitions for the authorization pattern used by
|
|
256
|
+
* {@link GuardedController}. Guards can be synchronous or asynchronous,
|
|
257
|
+
* enabling both simple checks and complex authorization logic.
|
|
258
|
+
*
|
|
259
|
+
* @example Synchronous guard (simple role check)
|
|
260
|
+
* ```typescript
|
|
261
|
+
* const adminGuard: AccessGuard<Request> = (req) => ({
|
|
262
|
+
* isAllowed: req.user?.role === 'admin',
|
|
263
|
+
* reason: 'Admin access required',
|
|
264
|
+
* });
|
|
265
|
+
* ```
|
|
266
|
+
*
|
|
267
|
+
* @example Asynchronous guard (database lookup)
|
|
268
|
+
* ```typescript
|
|
269
|
+
* const resourceOwnerGuard: AccessGuard<Request> = async (req) => {
|
|
270
|
+
* const resource = await db.findById(req.resourceId);
|
|
271
|
+
* return {
|
|
272
|
+
* isAllowed: resource?.ownerId === req.user?.id,
|
|
273
|
+
* reason: 'You do not own this resource',
|
|
274
|
+
* };
|
|
275
|
+
* };
|
|
276
|
+
* ```
|
|
277
|
+
*
|
|
278
|
+
* @module
|
|
279
|
+
*/
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Function type for authorization checks in controllers.
|
|
283
|
+
*
|
|
284
|
+
* An AccessGuard receives the incoming request and returns an
|
|
285
|
+
* {@link AccessGuardResult} indicating whether access should be granted.
|
|
286
|
+
* Guards can be synchronous or return a Promise for async operations.
|
|
287
|
+
*
|
|
288
|
+
* @typeParam T - The request type being guarded (defaults to `unknown`)
|
|
289
|
+
* @param request - The incoming request to evaluate
|
|
290
|
+
* @returns An {@link AccessGuardResult} or Promise resolving to one
|
|
291
|
+
*
|
|
292
|
+
* @example With GuardedController.create()
|
|
293
|
+
* ```typescript
|
|
294
|
+
* const controller = GuardedController.create({
|
|
295
|
+
* accessGuard: (req) => ({
|
|
296
|
+
* isAllowed: req.authenticated,
|
|
297
|
+
* reason: 'Authentication required',
|
|
298
|
+
* }),
|
|
299
|
+
* requestMapper: (req) => MyInputDto.create(req),
|
|
300
|
+
* useCase: myUseCase,
|
|
301
|
+
* responseMapper: (out) => MyOutputDto.create(out),
|
|
302
|
+
* });
|
|
303
|
+
* ```
|
|
304
|
+
*/
|
|
305
|
+
type AccessGuard<T = unknown> = (request: T) => AccessGuardResult | Promise<AccessGuardResult>;
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Configuration for creating a GuardedController instance.
|
|
309
|
+
*
|
|
310
|
+
* All types must be DTOs (extending BaseDto) to ensure validation at every boundary.
|
|
311
|
+
*
|
|
312
|
+
* @typeParam TRequestDto - Validated request DTO from the framework layer
|
|
313
|
+
* @typeParam TResponseDto - Validated response DTO to return to the framework
|
|
314
|
+
* @typeParam TInDto - Input DTO type for the use case
|
|
315
|
+
* @typeParam TOutDto - Output DTO type from the use case
|
|
316
|
+
*/
|
|
317
|
+
interface GuardedControllerConfig<TRequestDto extends BaseDto<unknown>, TResponseDto extends BaseDto<unknown>, TInDto extends BaseDto<unknown>, TOutDto extends BaseDto<unknown>> {
|
|
318
|
+
/** Maps the validated request DTO to a use case input DTO. */
|
|
319
|
+
requestMapper: (request: TRequestDto) => TInDto;
|
|
320
|
+
/** The use case to execute. */
|
|
321
|
+
useCase: BaseInboundPort<TInDto, TOutDto>;
|
|
322
|
+
/** Maps the use case output DTO to a validated response DTO. */
|
|
323
|
+
responseMapper: (output: TOutDto) => TResponseDto;
|
|
324
|
+
/** Optional access guard; defaults to allowing all requests. */
|
|
325
|
+
accessGuard?: AccessGuard<TRequestDto>;
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* Controller with access control.
|
|
329
|
+
*
|
|
330
|
+
* Extends {@link BaseController} with an access guard that runs before
|
|
331
|
+
* the pipeline. If the guard denies access, an {@link AccessDeniedError}
|
|
332
|
+
* is thrown.
|
|
333
|
+
*
|
|
334
|
+
* All types must be DTOs (extending BaseDto) to ensure validation at every boundary.
|
|
335
|
+
*
|
|
336
|
+
* @typeParam TRequestDto - Validated request DTO from the framework layer
|
|
337
|
+
* @typeParam TResponseDto - Validated response DTO to return to the framework
|
|
338
|
+
* @typeParam TInDto - Input DTO type for the use case
|
|
339
|
+
* @typeParam TOutDto - Output DTO type from the use case
|
|
340
|
+
*
|
|
341
|
+
* @example
|
|
342
|
+
* ```typescript
|
|
343
|
+
* const controller = GuardedController.create({
|
|
344
|
+
* requestMapper: (req) => UpdateUserInputDto.create(req.data),
|
|
345
|
+
* useCase: updateUserUseCase,
|
|
346
|
+
* responseMapper: (output) => UpdateUserResponseDto.create(output.data),
|
|
347
|
+
* accessGuard: async (req) => ({
|
|
348
|
+
* isAllowed: req.data.user?.role === 'admin',
|
|
349
|
+
* reason: 'Admin access required',
|
|
350
|
+
* }),
|
|
351
|
+
* });
|
|
352
|
+
* ```
|
|
353
|
+
*/
|
|
354
|
+
declare class GuardedController<TRequestDto extends BaseDto<unknown>, TResponseDto extends BaseDto<unknown>, TInDto extends BaseDto<unknown>, TOutDto extends BaseDto<unknown>> extends BaseController<TRequestDto, TResponseDto, TInDto, TOutDto> {
|
|
355
|
+
/** The access guard function for this controller. */
|
|
356
|
+
protected readonly accessGuard: AccessGuard<TRequestDto>;
|
|
357
|
+
/**
|
|
358
|
+
* Creates a new GuardedController instance.
|
|
359
|
+
*
|
|
360
|
+
* @param requestMapper - Function to map request DTO to use case input DTO
|
|
361
|
+
* @param useCase - The use case port to execute
|
|
362
|
+
* @param responseMapper - Function to map use case output DTO to response DTO
|
|
363
|
+
* @param accessGuard - Optional access guard; defaults to allowing all
|
|
364
|
+
*/
|
|
365
|
+
constructor(requestMapper: (request: TRequestDto) => TInDto, useCase: BaseInboundPort<TInDto, TOutDto>, responseMapper: (output: TOutDto) => TResponseDto, accessGuard?: AccessGuard<TRequestDto>);
|
|
366
|
+
/**
|
|
367
|
+
* Factory method to create a guarded controller from a configuration object.
|
|
368
|
+
*
|
|
369
|
+
* @param config - Controller configuration including optional access guard
|
|
370
|
+
* @returns A new GuardedController instance
|
|
371
|
+
*/
|
|
372
|
+
static create<TRequestDto extends BaseDto<unknown>, TResponseDto extends BaseDto<unknown>, TInDto extends BaseDto<unknown>, TOutDto extends BaseDto<unknown>>(config: GuardedControllerConfig<TRequestDto, TResponseDto, TInDto, TOutDto>): GuardedController<TRequestDto, TResponseDto, TInDto, TOutDto>;
|
|
373
|
+
/**
|
|
374
|
+
* Runs the controller pipeline with access control.
|
|
375
|
+
*
|
|
376
|
+
* Checks the access guard before executing the pipeline.
|
|
377
|
+
* If denied, throws {@link AccessDeniedError}.
|
|
378
|
+
*
|
|
379
|
+
* @param input - The validated request DTO
|
|
380
|
+
* @returns Promise resolving to the validated response DTO
|
|
381
|
+
* @throws {AccessDeniedError} When access guard denies the request
|
|
382
|
+
*/
|
|
383
|
+
protected pipeline(input: TRequestDto): Promise<TResponseDto>;
|
|
384
|
+
/**
|
|
385
|
+
* Checks access using the configured guard.
|
|
386
|
+
*
|
|
387
|
+
* Override to customize access control logic.
|
|
388
|
+
*
|
|
389
|
+
* @param input - The validated request DTO
|
|
390
|
+
* @throws {AccessDeniedError} When access is denied
|
|
391
|
+
*/
|
|
392
|
+
protected checkAccess(input: TRequestDto): Promise<void>;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* Error thrown when access to a resource is denied.
|
|
397
|
+
*
|
|
398
|
+
* Indicates that the requester does not have permission to perform
|
|
399
|
+
* the requested operation. Thrown by {@link GuardedController} when
|
|
400
|
+
* an access guard returns `isAllowed: false`.
|
|
401
|
+
*
|
|
402
|
+
* **When thrown:**
|
|
403
|
+
* - Access guard denies the request
|
|
404
|
+
* - User lacks required permissions
|
|
405
|
+
* - Resource ownership check fails
|
|
406
|
+
*
|
|
407
|
+
* @example GuardedController usage
|
|
408
|
+
* ```typescript
|
|
409
|
+
* const controller = GuardedController.create({
|
|
410
|
+
* accessGuard: (req) => ({
|
|
411
|
+
* isAllowed: req.user?.role === 'admin',
|
|
412
|
+
* reason: 'Admin access required',
|
|
413
|
+
* }),
|
|
414
|
+
* // ... other config
|
|
415
|
+
* });
|
|
416
|
+
* ```
|
|
417
|
+
*
|
|
418
|
+
* @example Manual usage
|
|
419
|
+
* ```typescript
|
|
420
|
+
* if (!user.canAccess(resource)) {
|
|
421
|
+
* throw new AccessDeniedError({
|
|
422
|
+
* message: 'You do not have access to this resource',
|
|
423
|
+
* code: 'RESOURCE_ACCESS_DENIED',
|
|
424
|
+
* });
|
|
425
|
+
* }
|
|
426
|
+
* ```
|
|
427
|
+
*
|
|
428
|
+
* @extends CodedError
|
|
429
|
+
*/
|
|
430
|
+
declare class AccessDeniedError extends CodedError {
|
|
431
|
+
/**
|
|
432
|
+
* Creates a new AccessDeniedError instance.
|
|
433
|
+
*
|
|
434
|
+
* @param options - Error configuration
|
|
435
|
+
* @param options.message - Description of why access was denied
|
|
436
|
+
* @param options.code - Machine-readable error code (default: 'ACCESS_DENIED')
|
|
437
|
+
* @param options.cause - Optional underlying error
|
|
438
|
+
*/
|
|
439
|
+
constructor({ message, code, cause, }: {
|
|
440
|
+
message: string;
|
|
441
|
+
code?: PresentationErrorCode | string;
|
|
442
|
+
cause?: unknown;
|
|
443
|
+
});
|
|
444
|
+
/**
|
|
445
|
+
* Creates an AccessDeniedError from a caught error.
|
|
446
|
+
*
|
|
447
|
+
* @param cause - The original caught error
|
|
448
|
+
* @returns A new AccessDeniedError instance with the cause attached
|
|
449
|
+
*/
|
|
450
|
+
static fromError(cause: unknown): AccessDeniedError;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
/**
|
|
454
|
+
* Base error class for presentation layer (controller) failures.
|
|
455
|
+
*
|
|
456
|
+
* Controller errors represent failures in request handling,
|
|
457
|
+
* such as access control violations or malformed requests.
|
|
458
|
+
* They are the outermost error layer and typically map to HTTP responses.
|
|
459
|
+
*
|
|
460
|
+
* **When to throw:**
|
|
461
|
+
* - Access control failures (unauthorized/forbidden)
|
|
462
|
+
* - Request validation failures
|
|
463
|
+
* - Unexpected controller execution errors
|
|
464
|
+
*
|
|
465
|
+
* **Child classes:**
|
|
466
|
+
* - {@link AccessDeniedError} - Authorization failures (HTTP 403)
|
|
467
|
+
* - {@link InvalidRequestError} - Request validation failures (HTTP 400)
|
|
468
|
+
*
|
|
469
|
+
* @example
|
|
470
|
+
* ```typescript
|
|
471
|
+
* // Thrown automatically by BaseController for unexpected errors
|
|
472
|
+
* throw new ControllerError({
|
|
473
|
+
* message: 'Controller execution failed',
|
|
474
|
+
* cause: originalError,
|
|
475
|
+
* });
|
|
476
|
+
* ```
|
|
477
|
+
*/
|
|
478
|
+
declare class ControllerError extends CodedError {
|
|
479
|
+
/**
|
|
480
|
+
* Creates a new ControllerError instance.
|
|
481
|
+
*
|
|
482
|
+
* @param options - Error configuration
|
|
483
|
+
* @param options.message - Human-readable error description
|
|
484
|
+
* @param options.code - Machine-readable error code (default: 'CONTROLLER_ERROR')
|
|
485
|
+
* @param options.cause - Optional underlying error
|
|
486
|
+
*/
|
|
487
|
+
constructor({ message, code, cause, }: {
|
|
488
|
+
message: string;
|
|
489
|
+
code?: PresentationErrorCode | string;
|
|
490
|
+
cause?: unknown;
|
|
491
|
+
});
|
|
492
|
+
/**
|
|
493
|
+
* Creates a ControllerError from a caught error.
|
|
494
|
+
*
|
|
495
|
+
* @param cause - The original caught error
|
|
496
|
+
* @returns A new ControllerError instance with the cause attached
|
|
497
|
+
*/
|
|
498
|
+
static fromError(cause: unknown): ControllerError;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
/**
|
|
502
|
+
* Error thrown when request validation fails at the controller level.
|
|
503
|
+
*
|
|
504
|
+
* Contains structured validation errors with field paths and messages,
|
|
505
|
+
* converted from {@link ObjectValidationError} by {@link BaseController}.
|
|
506
|
+
* Provides detailed feedback about which fields failed validation.
|
|
507
|
+
*
|
|
508
|
+
* **When thrown:**
|
|
509
|
+
* - Request DTO validation fails
|
|
510
|
+
* - Malformed request data
|
|
511
|
+
* - Missing required fields
|
|
512
|
+
*
|
|
513
|
+
* @example
|
|
514
|
+
* ```typescript
|
|
515
|
+
* // Automatically thrown by BaseController when DTO validation fails
|
|
516
|
+
* // The validationErrors array contains field-level details:
|
|
517
|
+
* // [
|
|
518
|
+
* // { field: 'email', message: 'Invalid email format' },
|
|
519
|
+
* // { field: 'age', message: 'Must be a positive number' }
|
|
520
|
+
* // ]
|
|
521
|
+
* ```
|
|
522
|
+
*
|
|
523
|
+
* @example Manual usage
|
|
524
|
+
* ```typescript
|
|
525
|
+
* throw new InvalidRequestError({
|
|
526
|
+
* message: 'Request validation failed',
|
|
527
|
+
* validationErrors: [
|
|
528
|
+
* { field: 'username', message: 'Username is required' },
|
|
529
|
+
* ],
|
|
530
|
+
* });
|
|
531
|
+
* ```
|
|
532
|
+
*
|
|
533
|
+
* @extends CodedError
|
|
534
|
+
*/
|
|
535
|
+
declare class InvalidRequestError extends CodedError {
|
|
536
|
+
/**
|
|
537
|
+
* Array of field-level validation errors.
|
|
538
|
+
*
|
|
539
|
+
* Each entry contains:
|
|
540
|
+
* - `field`: Dot-notation path to the invalid field
|
|
541
|
+
* - `message`: Human-readable validation failure message
|
|
542
|
+
*/
|
|
543
|
+
readonly validationErrors: ValidationError[];
|
|
544
|
+
/**
|
|
545
|
+
* Creates a new InvalidRequestError instance.
|
|
546
|
+
*
|
|
547
|
+
* @param options - Error configuration
|
|
548
|
+
* @param options.message - Summary of the validation failure
|
|
549
|
+
* @param options.code - Machine-readable error code (default: 'INVALID_REQUEST')
|
|
550
|
+
* @param options.cause - Optional underlying error
|
|
551
|
+
* @param options.validationErrors - Array of field-level validation errors
|
|
552
|
+
*/
|
|
553
|
+
constructor({ message, code, cause, validationErrors, }: {
|
|
554
|
+
message: string;
|
|
555
|
+
code?: PresentationErrorCode | string;
|
|
556
|
+
cause?: unknown;
|
|
557
|
+
validationErrors: ValidationError[];
|
|
558
|
+
});
|
|
559
|
+
/**
|
|
560
|
+
* Creates an InvalidRequestError from a caught error.
|
|
561
|
+
*
|
|
562
|
+
* @param cause - The original caught error
|
|
563
|
+
* @returns A new InvalidRequestError instance with the cause attached
|
|
564
|
+
*/
|
|
565
|
+
static fromError(cause: unknown): InvalidRequestError;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
/**
|
|
569
|
+
* Enhanced request wrapper with metadata and context.
|
|
570
|
+
*
|
|
571
|
+
* Provides a generic structure for wrapping any request type with
|
|
572
|
+
* additional metadata (e.g., trace ID, timestamp) and execution context
|
|
573
|
+
* (e.g., authenticated user, permissions).
|
|
574
|
+
*
|
|
575
|
+
* This is a presentation-layer concern as it represents how external
|
|
576
|
+
* systems (HTTP, Kafka, WebSocket, gRPC, CLI, etc.) interact with
|
|
577
|
+
* our services.
|
|
578
|
+
*
|
|
579
|
+
* @typeParam TMetadata - Request metadata type (e.g., trace ID, correlation ID)
|
|
580
|
+
* @typeParam TContext - Execution context type (e.g., auth user, API key, permissions)
|
|
581
|
+
* @typeParam TRequest - The actual request payload type
|
|
582
|
+
*
|
|
583
|
+
* @example
|
|
584
|
+
* ```typescript
|
|
585
|
+
* interface RequestMetadata {
|
|
586
|
+
* traceId: string;
|
|
587
|
+
* timestamp: Date;
|
|
588
|
+
* }
|
|
589
|
+
*
|
|
590
|
+
* interface UserContext {
|
|
591
|
+
* userId: string;
|
|
592
|
+
* roles: string[];
|
|
593
|
+
* }
|
|
594
|
+
*
|
|
595
|
+
* type MyEnhancedRequest = EnhancedRequest<RequestMetadata, UserContext, HttpRequest>;
|
|
596
|
+
*
|
|
597
|
+
* const request: MyEnhancedRequest = {
|
|
598
|
+
* metadata: { traceId: 'abc-123', timestamp: new Date() },
|
|
599
|
+
* context: { userId: 'user-1', roles: ['admin'] },
|
|
600
|
+
* request: { body: { name: 'John' } },
|
|
601
|
+
* };
|
|
602
|
+
* ```
|
|
603
|
+
*/
|
|
604
|
+
interface EnhancedRequest<TMetadata, TContext, TRequest> {
|
|
605
|
+
/**
|
|
606
|
+
* Request metadata containing operational information.
|
|
607
|
+
* Typically includes trace IDs, timestamps, and other observability data.
|
|
608
|
+
*/
|
|
609
|
+
metadata: TMetadata;
|
|
610
|
+
/**
|
|
611
|
+
* Execution context containing authorization and identity information.
|
|
612
|
+
* Typically includes authenticated user data, permissions, and API key info.
|
|
613
|
+
*/
|
|
614
|
+
context: TContext;
|
|
615
|
+
/**
|
|
616
|
+
* The actual request payload.
|
|
617
|
+
* Contains the business-relevant data from the incoming request.
|
|
618
|
+
*/
|
|
619
|
+
request: TRequest;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
/**
|
|
623
|
+
* Enhanced HTTP request with metadata and context.
|
|
624
|
+
*
|
|
625
|
+
* A specialized version of {@link EnhancedRequest} where the request payload
|
|
626
|
+
* is fixed to {@link HttpRequest}. This is the standard type for HTTP-based
|
|
627
|
+
* frameworks (AWS API Gateway, Cloudflare Workers, Express, etc.).
|
|
628
|
+
*
|
|
629
|
+
* @typeParam TMetadata - Request metadata type (e.g., request ID, correlation ID)
|
|
630
|
+
* @typeParam TContext - Execution context type (e.g., authenticated user, API key)
|
|
631
|
+
*
|
|
632
|
+
* @example AWS API Gateway usage
|
|
633
|
+
* ```typescript
|
|
634
|
+
* interface AwsMetadata {
|
|
635
|
+
* requestId: string;
|
|
636
|
+
* stage: string;
|
|
637
|
+
* }
|
|
638
|
+
*
|
|
639
|
+
* interface AuthorizerContext {
|
|
640
|
+
* userId: string;
|
|
641
|
+
* permissions: string[];
|
|
642
|
+
* }
|
|
643
|
+
*
|
|
644
|
+
* type AwsEnhancedRequest = EnhancedHttpRequest<AwsMetadata, AuthorizerContext>;
|
|
645
|
+
*
|
|
646
|
+
* const request: AwsEnhancedRequest = {
|
|
647
|
+
* metadata: { requestId: 'req-123', stage: 'prod' },
|
|
648
|
+
* context: { userId: 'user-1', permissions: ['read', 'write'] },
|
|
649
|
+
* request: {
|
|
650
|
+
* body: { name: 'John' },
|
|
651
|
+
* headers: { 'content-type': 'application/json' },
|
|
652
|
+
* pathParams: { id: '123' },
|
|
653
|
+
* queryParams: { limit: '10' },
|
|
654
|
+
* },
|
|
655
|
+
* };
|
|
656
|
+
* ```
|
|
657
|
+
*/
|
|
658
|
+
type EnhancedHttpRequest<TMetadata, TContext> = EnhancedRequest<TMetadata, TContext, HttpRequest>;
|
|
659
|
+
|
|
660
|
+
/**
|
|
661
|
+
* Service metadata.
|
|
662
|
+
*
|
|
663
|
+
* Describes a service (bounded context / API surface) in a stable, serializable way.
|
|
664
|
+
*
|
|
665
|
+
* Intended use:
|
|
666
|
+
* - building OpenAPI specs (grouping, base paths)
|
|
667
|
+
* - generating clients/docs
|
|
668
|
+
* - consistent identification across repos
|
|
669
|
+
*/
|
|
670
|
+
interface ServiceMetadata {
|
|
671
|
+
/**
|
|
672
|
+
* Stable identifier for the service.
|
|
673
|
+
*
|
|
674
|
+
* Recommendation: kebab-case and unique across the system.
|
|
675
|
+
* Example: `user-service`
|
|
676
|
+
*/
|
|
677
|
+
id: string;
|
|
678
|
+
/**
|
|
679
|
+
* Short identifier used in logs, URLs, or UI labels.
|
|
680
|
+
*
|
|
681
|
+
* Recommendation: short kebab-case.
|
|
682
|
+
* Example: `user`
|
|
683
|
+
*/
|
|
684
|
+
shortId: string;
|
|
685
|
+
/**
|
|
686
|
+
* Human-readable service name.
|
|
687
|
+
*
|
|
688
|
+
* Example: `User Service`
|
|
689
|
+
*/
|
|
690
|
+
name: string;
|
|
691
|
+
/**
|
|
692
|
+
* Human-readable description of the service.
|
|
693
|
+
*/
|
|
694
|
+
description: string;
|
|
695
|
+
/**
|
|
696
|
+
* Service base path prefix used to namespace HTTP routes.
|
|
697
|
+
*
|
|
698
|
+
* Example: `/user-service`
|
|
699
|
+
*/
|
|
700
|
+
basePath: string;
|
|
701
|
+
/**
|
|
702
|
+
* OpenAPI-specific configuration for per-service spec generation.
|
|
703
|
+
*/
|
|
704
|
+
openApi: {
|
|
705
|
+
/**
|
|
706
|
+
* OpenAPI info.title for per-service spec.
|
|
707
|
+
*
|
|
708
|
+
* Example: `User Service API`
|
|
709
|
+
*/
|
|
710
|
+
title: string;
|
|
711
|
+
/**
|
|
712
|
+
* OpenAPI info.description for per-service spec.
|
|
713
|
+
* If not set, uses the service's `description`.
|
|
714
|
+
*/
|
|
715
|
+
description?: string;
|
|
716
|
+
};
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
/**
|
|
720
|
+
* Resource metadata.
|
|
721
|
+
*
|
|
722
|
+
* A "resource" is a group of endpoints inside a service (often aligned with a domain aggregate
|
|
723
|
+
* or a REST-ish resource folder like `organization-membership-invite`).
|
|
724
|
+
*
|
|
725
|
+
* Intended use:
|
|
726
|
+
* - OpenAPI tags (2nd-level grouping)
|
|
727
|
+
* - stable tag naming/ordering independent of folder name changes
|
|
728
|
+
*/
|
|
729
|
+
interface ResourceMetadata {
|
|
730
|
+
/**
|
|
731
|
+
* Stable identifier for the resource.
|
|
732
|
+
*
|
|
733
|
+
* Recommendation: kebab-case and matches its folder name.
|
|
734
|
+
* Example: `organization-membership-invite`
|
|
735
|
+
*/
|
|
736
|
+
id: string;
|
|
737
|
+
/**
|
|
738
|
+
* Short identifier used in logs or metrics.
|
|
739
|
+
*
|
|
740
|
+
* Recommendation: short kebab-case.
|
|
741
|
+
* Example: `org-invite`
|
|
742
|
+
*/
|
|
743
|
+
shortId: string;
|
|
744
|
+
/**
|
|
745
|
+
* Human-readable resource name.
|
|
746
|
+
*
|
|
747
|
+
* Example: `Organization Membership Invite`
|
|
748
|
+
*/
|
|
749
|
+
name: string;
|
|
750
|
+
/**
|
|
751
|
+
* Human-readable description of the resource.
|
|
752
|
+
*/
|
|
753
|
+
description: string;
|
|
754
|
+
/**
|
|
755
|
+
* Resource path segment (relative to service basePath).
|
|
756
|
+
*
|
|
757
|
+
* Combined with service basePath and endpoint path to form the full route.
|
|
758
|
+
*
|
|
759
|
+
* Example: `/users`
|
|
760
|
+
*/
|
|
761
|
+
path: string;
|
|
762
|
+
/**
|
|
763
|
+
* Explicit ordering for UI grouping within the service.
|
|
764
|
+
*
|
|
765
|
+
* Lower numbers come first.
|
|
766
|
+
*/
|
|
767
|
+
order: number;
|
|
768
|
+
/**
|
|
769
|
+
* OpenAPI-specific configuration for tag generation.
|
|
770
|
+
*/
|
|
771
|
+
openApi: {
|
|
772
|
+
/**
|
|
773
|
+
* OpenAPI tag display name.
|
|
774
|
+
*
|
|
775
|
+
* Example: `Organization Membership Invite`
|
|
776
|
+
*/
|
|
777
|
+
tag: string;
|
|
778
|
+
/**
|
|
779
|
+
* OpenAPI tag description shown in documentation.
|
|
780
|
+
*/
|
|
781
|
+
tagDescription: string;
|
|
782
|
+
};
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
/**
|
|
786
|
+
* System metadata.
|
|
787
|
+
*
|
|
788
|
+
* Top-level metadata for the whole API surface ("the system"), sitting above
|
|
789
|
+
* individual services/resources/endpoints.
|
|
790
|
+
*
|
|
791
|
+
* This is intentionally stable and serializable so it can be consumed by tools
|
|
792
|
+
* (OpenAPI generation, docs, code generation).
|
|
793
|
+
*/
|
|
794
|
+
/**
|
|
795
|
+
* API Key authentication scheme (header, query, or cookie).
|
|
796
|
+
*/
|
|
797
|
+
interface ApiKeyAuthScheme {
|
|
798
|
+
type: 'apiKey';
|
|
799
|
+
in: 'header' | 'query' | 'cookie';
|
|
800
|
+
/** Header/query/cookie parameter name, e.g. 'x-api-key' */
|
|
801
|
+
name: string;
|
|
802
|
+
description?: string;
|
|
803
|
+
}
|
|
804
|
+
/**
|
|
805
|
+
* HTTP Bearer authentication scheme.
|
|
806
|
+
*/
|
|
807
|
+
interface HttpBearerAuthScheme {
|
|
808
|
+
type: 'http';
|
|
809
|
+
scheme: 'bearer';
|
|
810
|
+
bearerFormat?: string;
|
|
811
|
+
description?: string;
|
|
812
|
+
}
|
|
813
|
+
/**
|
|
814
|
+
* HTTP Basic authentication scheme.
|
|
815
|
+
*/
|
|
816
|
+
interface HttpBasicAuthScheme {
|
|
817
|
+
type: 'http';
|
|
818
|
+
scheme: 'basic';
|
|
819
|
+
description?: string;
|
|
820
|
+
}
|
|
821
|
+
/**
|
|
822
|
+
* Union of all supported authentication schemes.
|
|
823
|
+
*/
|
|
824
|
+
type AuthScheme = ApiKeyAuthScheme | HttpBearerAuthScheme | HttpBasicAuthScheme;
|
|
825
|
+
interface SystemMetadata {
|
|
826
|
+
/**
|
|
827
|
+
* Stable system identifier.
|
|
828
|
+
*
|
|
829
|
+
* Recommendation: kebab-case.
|
|
830
|
+
* Example: `acmp-connector`
|
|
831
|
+
*/
|
|
832
|
+
id: string;
|
|
833
|
+
/**
|
|
834
|
+
* Short identifier for compact displays/logging.
|
|
835
|
+
*
|
|
836
|
+
* Example: `acmp`
|
|
837
|
+
*/
|
|
838
|
+
shortId: string;
|
|
839
|
+
/**
|
|
840
|
+
* Human-readable system name.
|
|
841
|
+
*
|
|
842
|
+
* Example: `ACMP Connector`
|
|
843
|
+
*/
|
|
844
|
+
name: string;
|
|
845
|
+
/**
|
|
846
|
+
* Human-readable description.
|
|
847
|
+
*/
|
|
848
|
+
description?: string;
|
|
849
|
+
/**
|
|
850
|
+
* System/API version (SemVer recommended).
|
|
851
|
+
*
|
|
852
|
+
* Example: `1.0.0`
|
|
853
|
+
*/
|
|
854
|
+
version: string;
|
|
855
|
+
/**
|
|
856
|
+
* Default servers for the API.
|
|
857
|
+
* Used by OpenAPI generation and client configuration.
|
|
858
|
+
*/
|
|
859
|
+
servers?: {
|
|
860
|
+
url: string;
|
|
861
|
+
description?: string;
|
|
862
|
+
}[];
|
|
863
|
+
/**
|
|
864
|
+
* Authentication configuration.
|
|
865
|
+
*/
|
|
866
|
+
auth?: {
|
|
867
|
+
/**
|
|
868
|
+
* Whether endpoints should be considered secure by default.
|
|
869
|
+
* @default true
|
|
870
|
+
*/
|
|
871
|
+
secureByDefault?: boolean;
|
|
872
|
+
/**
|
|
873
|
+
* Available authentication schemes.
|
|
874
|
+
*
|
|
875
|
+
* Keyed by scheme name (must be unique within the system).
|
|
876
|
+
* Example: `{ apiKeyAuth: { type: 'apiKey', in: 'header', name: 'x-api-key' } }`
|
|
877
|
+
*/
|
|
878
|
+
schemes: Record<string, AuthScheme>;
|
|
879
|
+
/**
|
|
880
|
+
* Default security requirements (applies to all secure endpoints).
|
|
881
|
+
*
|
|
882
|
+
* OpenAPI semantics:
|
|
883
|
+
* - Array is OR (any requirement can satisfy)
|
|
884
|
+
* - Object is AND (all schemes required together)
|
|
885
|
+
*
|
|
886
|
+
* Example: `[{ apiKeyAuth: [] }]`
|
|
887
|
+
*/
|
|
888
|
+
defaultSecurity?: Record<string, string[]>[];
|
|
889
|
+
};
|
|
890
|
+
/**
|
|
891
|
+
* OpenAPI-specific overrides for global spec generation.
|
|
892
|
+
*/
|
|
893
|
+
openApi?: {
|
|
894
|
+
/**
|
|
895
|
+
* OpenAPI info.title.
|
|
896
|
+
* Overrides `name` if set.
|
|
897
|
+
*/
|
|
898
|
+
title?: string;
|
|
899
|
+
/**
|
|
900
|
+
* OpenAPI info.description.
|
|
901
|
+
* Overrides `description` if set.
|
|
902
|
+
*/
|
|
903
|
+
description?: string;
|
|
904
|
+
/**
|
|
905
|
+
* OpenAPI info.termsOfService URL.
|
|
906
|
+
*/
|
|
907
|
+
termsOfService?: string;
|
|
908
|
+
/**
|
|
909
|
+
* OpenAPI info.contact.
|
|
910
|
+
*/
|
|
911
|
+
contact?: {
|
|
912
|
+
name?: string;
|
|
913
|
+
url?: string;
|
|
914
|
+
email?: string;
|
|
915
|
+
};
|
|
916
|
+
/**
|
|
917
|
+
* OpenAPI info.license.
|
|
918
|
+
*/
|
|
919
|
+
license?: {
|
|
920
|
+
name: string;
|
|
921
|
+
url?: string;
|
|
922
|
+
};
|
|
923
|
+
};
|
|
924
|
+
}
|
|
925
|
+
/**
|
|
926
|
+
* Extracts the scheme names (keys) from a SystemMetadata constant.
|
|
927
|
+
*
|
|
928
|
+
* @example
|
|
929
|
+
* const systemMeta = { auth: { schemes: { apiKeyAuth: {...}, bearerAuth: {...} } } } as const;
|
|
930
|
+
* type Names = SchemeNamesOf<typeof systemMeta>; // 'apiKeyAuth' | 'bearerAuth'
|
|
931
|
+
*/
|
|
932
|
+
type SchemeNamesOf<TSystem> = TSystem extends {
|
|
933
|
+
auth: {
|
|
934
|
+
schemes: infer S;
|
|
935
|
+
};
|
|
936
|
+
} ? keyof S & string : never;
|
|
937
|
+
/**
|
|
938
|
+
* Creates a typed OpenAPI security requirement array from scheme names.
|
|
939
|
+
*
|
|
940
|
+
* @example
|
|
941
|
+
* type Req = SecurityRequirementOf<'apiKeyAuth' | 'bearerAuth'>;
|
|
942
|
+
* // Array<Partial<Record<'apiKeyAuth' | 'bearerAuth', string[]>>>
|
|
943
|
+
*/
|
|
944
|
+
type SecurityRequirementOf<TSchemeNames extends string> = Partial<Record<TSchemeNames, string[]>>[];
|
|
945
|
+
/**
|
|
946
|
+
* Input type for defineSystemMetadata helper (with generic auth).
|
|
947
|
+
*/
|
|
948
|
+
type SystemMetadataInput<TSchemeNames extends string> = Omit<SystemMetadata, 'auth'> & {
|
|
949
|
+
auth?: {
|
|
950
|
+
secureByDefault?: boolean;
|
|
951
|
+
schemes: Record<TSchemeNames, AuthScheme>;
|
|
952
|
+
defaultSecurity?: Partial<Record<TSchemeNames, string[]>>[];
|
|
953
|
+
};
|
|
954
|
+
};
|
|
955
|
+
/**
|
|
956
|
+
* Defines system metadata with fully-typed `defaultSecurity`.
|
|
957
|
+
*
|
|
958
|
+
* Infers scheme names from the `schemes` object and validates that
|
|
959
|
+
* `defaultSecurity` only references existing scheme names.
|
|
960
|
+
*
|
|
961
|
+
* @example
|
|
962
|
+
* export const systemMetadata = defineSystemMetadata({
|
|
963
|
+
* id: 'my-api',
|
|
964
|
+
* shortId: 'api',
|
|
965
|
+
* name: 'My API',
|
|
966
|
+
* version: '1.0.0',
|
|
967
|
+
* auth: {
|
|
968
|
+
* schemes: {
|
|
969
|
+
* apiKeyAuth: { type: 'apiKey', in: 'header', name: 'x-api-key' },
|
|
970
|
+
* },
|
|
971
|
+
* defaultSecurity: [{ apiKeyAuth: [] }], // ✅ Valid
|
|
972
|
+
* // defaultSecurity: [{ typoAuth: [] }], // ❌ TypeScript error!
|
|
973
|
+
* },
|
|
974
|
+
* });
|
|
975
|
+
*/
|
|
976
|
+
declare function defineSystemMetadata<const TSchemeNames extends string>(metadata: SystemMetadataInput<TSchemeNames>): SystemMetadataInput<TSchemeNames> & SystemMetadata;
|
|
977
|
+
|
|
978
|
+
/**
|
|
979
|
+
* Allowed HTTP methods for endpoint metadata.
|
|
980
|
+
*/
|
|
981
|
+
type HttpEndpointMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
|
|
982
|
+
/**
|
|
983
|
+
* Allowed HTTP success status codes.
|
|
984
|
+
*/
|
|
985
|
+
type HttpSuccessStatus = 200 | 201 | 202 | 204;
|
|
986
|
+
/**
|
|
987
|
+
* OpenAPI-specific fields for endpoint metadata (loosely typed).
|
|
988
|
+
*/
|
|
989
|
+
interface HttpEndpointOpenApi {
|
|
990
|
+
/**
|
|
991
|
+
* OpenAPI operation summary.
|
|
992
|
+
* Overrides `description` for the summary field if set.
|
|
993
|
+
*/
|
|
994
|
+
summary?: string;
|
|
995
|
+
/**
|
|
996
|
+
* OpenAPI operation description (detailed).
|
|
997
|
+
* Overrides `description` for the description field if set.
|
|
998
|
+
*/
|
|
999
|
+
description?: string;
|
|
1000
|
+
/**
|
|
1001
|
+
* Explicit OpenAPI operationId.
|
|
1002
|
+
* Defaults to endpoint `name` if not set.
|
|
1003
|
+
*/
|
|
1004
|
+
operationId?: string;
|
|
1005
|
+
/**
|
|
1006
|
+
* Override resource tag(s) for this endpoint.
|
|
1007
|
+
* Defaults to resource's openApi.tag or name if not set.
|
|
1008
|
+
*/
|
|
1009
|
+
tags?: string[];
|
|
1010
|
+
/**
|
|
1011
|
+
* Mark endpoint as deprecated in OpenAPI documentation.
|
|
1012
|
+
* @default false
|
|
1013
|
+
*/
|
|
1014
|
+
deprecated?: boolean;
|
|
1015
|
+
/**
|
|
1016
|
+
* HTTP success status code for OpenAPI documentation.
|
|
1017
|
+
*
|
|
1018
|
+
* @default Derived from HTTP method:
|
|
1019
|
+
* - GET: 200
|
|
1020
|
+
* - POST: 201
|
|
1021
|
+
* - PUT: 200
|
|
1022
|
+
* - PATCH: 200
|
|
1023
|
+
* - DELETE: 204
|
|
1024
|
+
*/
|
|
1025
|
+
successStatus?: HttpSuccessStatus;
|
|
1026
|
+
/**
|
|
1027
|
+
* Explicit OpenAPI `security` requirements for this endpoint.
|
|
1028
|
+
*
|
|
1029
|
+
* OpenAPI semantics:
|
|
1030
|
+
* - The array is OR (any requirement can satisfy).
|
|
1031
|
+
* - Each object is AND (all schemes in the object are required together).
|
|
1032
|
+
*
|
|
1033
|
+
* Examples:
|
|
1034
|
+
* - Public endpoint (overrides any global security): `[]`
|
|
1035
|
+
* - Require api key: `[ { apiKeyAuth: [] } ]`
|
|
1036
|
+
* - Allow either api key OR bearer: `[ { apiKeyAuth: [] }, { bearerAuth: [] } ]`
|
|
1037
|
+
*/
|
|
1038
|
+
security?: Record<string, string[]>[];
|
|
1039
|
+
}
|
|
1040
|
+
/**
|
|
1041
|
+
* OpenAPI-specific fields for endpoint metadata (strongly typed security).
|
|
1042
|
+
*/
|
|
1043
|
+
interface HttpEndpointOpenApiFor<TSystem> extends Omit<HttpEndpointOpenApi, 'security'> {
|
|
1044
|
+
/**
|
|
1045
|
+
* Explicit OpenAPI `security` requirements for this endpoint.
|
|
1046
|
+
* Scheme names are validated against the system metadata at compile time.
|
|
1047
|
+
*/
|
|
1048
|
+
security?: SecurityRequirementOf<SchemeNamesOf<TSystem>>;
|
|
1049
|
+
}
|
|
1050
|
+
/**
|
|
1051
|
+
* HTTP endpoint metadata.
|
|
1052
|
+
*
|
|
1053
|
+
* Describes an HTTP endpoint in a stable, serializable way.
|
|
1054
|
+
*
|
|
1055
|
+
* Intended use:
|
|
1056
|
+
* - documentation (OpenAPI generation)
|
|
1057
|
+
* - HTTP client generation
|
|
1058
|
+
* - consistent endpoint identification (id/shortId)
|
|
1059
|
+
*/
|
|
1060
|
+
interface HttpEndpointMetadata {
|
|
1061
|
+
/**
|
|
1062
|
+
* Stable endpoint identifier.
|
|
1063
|
+
*
|
|
1064
|
+
* Recommendation: kebab-case.
|
|
1065
|
+
* Example: `get-user-by-id`
|
|
1066
|
+
*/
|
|
1067
|
+
id: string;
|
|
1068
|
+
/**
|
|
1069
|
+
* Short endpoint identifier.
|
|
1070
|
+
*
|
|
1071
|
+
* Useful for logs/metrics.
|
|
1072
|
+
* Example: `gubi`
|
|
1073
|
+
*/
|
|
1074
|
+
shortId: string;
|
|
1075
|
+
/**
|
|
1076
|
+
* Stable endpoint name used in code.
|
|
1077
|
+
*
|
|
1078
|
+
* Recommendation: camelCase.
|
|
1079
|
+
* Example: `getUserById`
|
|
1080
|
+
*/
|
|
1081
|
+
name: string;
|
|
1082
|
+
/**
|
|
1083
|
+
* Human-readable description.
|
|
1084
|
+
*
|
|
1085
|
+
* Used as default for OpenAPI summary/description.
|
|
1086
|
+
*/
|
|
1087
|
+
description: string;
|
|
1088
|
+
/**
|
|
1089
|
+
* Endpoint-relative path (without service or resource path).
|
|
1090
|
+
*
|
|
1091
|
+
* Combined with service basePath and resource path to form the full route.
|
|
1092
|
+
* Use `computeRoutePath()` to compute the full path when needed.
|
|
1093
|
+
*
|
|
1094
|
+
* Example: `/{id}` or `/` for collection endpoints
|
|
1095
|
+
*/
|
|
1096
|
+
path: string;
|
|
1097
|
+
/**
|
|
1098
|
+
* HTTP method.
|
|
1099
|
+
*/
|
|
1100
|
+
method: HttpEndpointMethod;
|
|
1101
|
+
/**
|
|
1102
|
+
* OpenAPI-specific overrides for this endpoint.
|
|
1103
|
+
*/
|
|
1104
|
+
openApi?: HttpEndpointOpenApi;
|
|
1105
|
+
}
|
|
1106
|
+
/**
|
|
1107
|
+
* HTTP endpoint metadata with strongly-typed `openApi.security`.
|
|
1108
|
+
*
|
|
1109
|
+
* Use this to get compile-time validation of security scheme names
|
|
1110
|
+
* against a SystemMetadata constant.
|
|
1111
|
+
*
|
|
1112
|
+
* @example
|
|
1113
|
+
* import type { HttpEndpointMetadataFor } from '@cosmneo/org-lib-backend-common-kit';
|
|
1114
|
+
* import { systemMetadata } from '@/contracts/system-metadata';
|
|
1115
|
+
*
|
|
1116
|
+
* export const getUserHttpMetadata: HttpEndpointMetadataFor<typeof systemMetadata> = {
|
|
1117
|
+
* id: 'get-user',
|
|
1118
|
+
* // ...
|
|
1119
|
+
* openApi: {
|
|
1120
|
+
* security: [{ apiKeyAuth: [] }], // ✅ Compile-time checked!
|
|
1121
|
+
* },
|
|
1122
|
+
* };
|
|
1123
|
+
*/
|
|
1124
|
+
interface HttpEndpointMetadataFor<TSystem> extends Omit<HttpEndpointMetadata, 'openApi'> {
|
|
1125
|
+
/**
|
|
1126
|
+
* OpenAPI-specific overrides for this endpoint (with typed security).
|
|
1127
|
+
*/
|
|
1128
|
+
openApi?: HttpEndpointOpenApiFor<TSystem>;
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
/**
|
|
1132
|
+
* Computes the full route path from service, resource, and endpoint metadata.
|
|
1133
|
+
*
|
|
1134
|
+
* Handles path normalization:
|
|
1135
|
+
* - Ensures leading slash
|
|
1136
|
+
* - Joins segments with single slashes
|
|
1137
|
+
* - Removes trailing slash (except for root)
|
|
1138
|
+
*
|
|
1139
|
+
* @example
|
|
1140
|
+
* ```typescript
|
|
1141
|
+
* computeRoutePath(
|
|
1142
|
+
* { basePath: '/user-service' },
|
|
1143
|
+
* { path: '/users' },
|
|
1144
|
+
* { path: '/{id}' }
|
|
1145
|
+
* ); // => '/user-service/users/{id}'
|
|
1146
|
+
* ```
|
|
1147
|
+
*/
|
|
1148
|
+
declare function computeRoutePath(service: Pick<ServiceMetadata, 'basePath'>, resource: Pick<ResourceMetadata, 'path'>, endpoint: Pick<HttpEndpointMetadata, 'path'>): string;
|
|
1149
|
+
|
|
1150
|
+
/**
|
|
1151
|
+
* Type guard to validate that a value is a valid HttpResponse.
|
|
1152
|
+
*
|
|
1153
|
+
* Checks that the value has the required `statusCode` property as a number.
|
|
1154
|
+
* This is used to validate controller outputs before passing to response mappers.
|
|
1155
|
+
*
|
|
1156
|
+
* @param value - The value to check
|
|
1157
|
+
* @returns True if the value is a valid HttpResponse
|
|
1158
|
+
*
|
|
1159
|
+
* @example
|
|
1160
|
+
* ```typescript
|
|
1161
|
+
* const output = controller.execute(input);
|
|
1162
|
+
* if (!isHttpResponse(output)) {
|
|
1163
|
+
* throw new Error('Controller must return an HttpResponse');
|
|
1164
|
+
* }
|
|
1165
|
+
* ```
|
|
1166
|
+
*/
|
|
1167
|
+
declare function isHttpResponse(value: unknown): value is HttpResponse;
|
|
1168
|
+
/**
|
|
1169
|
+
* Asserts that a value is a valid HttpResponse.
|
|
1170
|
+
*
|
|
1171
|
+
* Throws a descriptive error if the value is not a valid HttpResponse,
|
|
1172
|
+
* making it easier to debug controller output issues.
|
|
1173
|
+
*
|
|
1174
|
+
* @param value - The value to validate
|
|
1175
|
+
* @param context - Optional context for the error message (e.g., 'mapOutput')
|
|
1176
|
+
* @throws Error if the value is not a valid HttpResponse
|
|
1177
|
+
*
|
|
1178
|
+
* @example
|
|
1179
|
+
* ```typescript
|
|
1180
|
+
* const output = controller.execute(input);
|
|
1181
|
+
* assertHttpResponse(output, 'controller output');
|
|
1182
|
+
* // Now TypeScript knows output is HttpResponse
|
|
1183
|
+
* ```
|
|
1184
|
+
*/
|
|
1185
|
+
declare function assertHttpResponse(value: unknown, context?: string): asserts value is HttpResponse;
|
|
1186
|
+
|
|
1187
|
+
export { AccessDeniedError as A, type BaseInboundPort as B, ControllerError as C, type EnhancedHttpRequest as E, type GuardedControllerConfig as G, type HttpEndpointMethod as H, InvalidRequestError as I, type ResourceMetadata as R, type ServiceMetadata as S, type BaseControllerConfig as a, BaseController as b, GuardedController as c, type AccessGuard as d, type AccessGuardResult as e, type EnhancedRequest as f, type HttpSuccessStatus as g, type HttpEndpointOpenApi as h, type HttpEndpointOpenApiFor as i, type HttpEndpointMetadata as j, type HttpEndpointMetadataFor as k, type ApiKeyAuthScheme as l, type HttpBearerAuthScheme as m, type HttpBasicAuthScheme as n, type AuthScheme as o, type SystemMetadata as p, type SchemeNamesOf as q, type SecurityRequirementOf as r, defineSystemMetadata as s, computeRoutePath as t, isHttpResponse as u, assertHttpResponse as v };
|