@breadstone/archipel-mcp 0.0.16 → 0.0.18
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/data/guides/controller-best-practices.md +766 -0
- package/data/guides/index.md +11 -7
- package/data/guides/openapi-and-feature-discovery.md +20 -12
- package/data/guides/request-response-dtos.md +411 -0
- package/data/packages/platform-caching/index.md +8 -5
- package/data/packages/platform-cryptography/index.md +6 -8
- package/data/packages/platform-logging/index.md +18 -46
- package/data/packages/platform-openapi/api/Function.Api.md +3 -14
- package/data/packages/platform-openapi/api/index.md +1 -1
- package/data/packages/platform-openapi/index.md +78 -43
- package/package.json +1 -1
package/data/guides/index.md
CHANGED
|
@@ -9,13 +9,17 @@ Practical guides for working with Archipel packages in your NestJS application.
|
|
|
9
9
|
|
|
10
10
|
## Foundations
|
|
11
11
|
|
|
12
|
-
| Guide
|
|
13
|
-
|
|
|
14
|
-
| [Getting Started](./getting-started)
|
|
15
|
-
| [Configuration Management](./configuration)
|
|
16
|
-
| [Implementing Ports](./implementing-ports)
|
|
17
|
-
| [
|
|
18
|
-
| [
|
|
12
|
+
| Guide | What you'll learn |
|
|
13
|
+
| --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
|
|
14
|
+
| [Getting Started](./getting-started) | Install packages, register your first module, implement ports, and run the app. |
|
|
15
|
+
| [Configuration Management](./configuration) | Type-safe config keys, strategy-based resolution, central registry, and validation. |
|
|
16
|
+
| [Implementing Ports](./implementing-ports) | How the port/adapter pattern works, how to write adapters, and how to test them. |
|
|
17
|
+
| [Controller Best Practices](./controller-best-practices) | Structure, annotate, and implement controllers — regions, DI, ResponseReturn, guards, and the @Api decorator. |
|
|
18
|
+
| [Request & Response DTOs](./request-response-dtos) | DTO naming conventions, validation decorators, cross-field validation, and class-transformer integration. |
|
|
19
|
+
| [Testing](./testing) | Unit testing services, testing port adapters, integration testing with real databases. |
|
|
20
|
+
| [TSDoc Guidelines](./tsdoc-guidelines) | Documentation standards for public APIs and generated reference pages. ld validation, and class-transformer integration. |
|
|
21
|
+
| [Testing](./testing) | Unit testing services, testing port adapters, integration testing with real databases. |
|
|
22
|
+
| [TSDoc Guidelines](./tsdoc-guidelines) | Documentation standards for public APIs and generated reference pages. |
|
|
19
23
|
|
|
20
24
|
## Security & Identity
|
|
21
25
|
|
|
@@ -149,18 +149,26 @@ export class TipController {
|
|
|
149
149
|
|
|
150
150
|
### `@Api()` Options Reference
|
|
151
151
|
|
|
152
|
-
| Option | Type
|
|
153
|
-
| ------------ |
|
|
154
|
-
| `auth` | `
|
|
155
|
-
| `tags` | `string \| string[]`
|
|
156
|
-
| `operation` | `ApiOperationOptions`
|
|
157
|
-
| `
|
|
158
|
-
| `
|
|
159
|
-
| `
|
|
160
|
-
| `
|
|
161
|
-
| `
|
|
162
|
-
| `
|
|
163
|
-
| `
|
|
152
|
+
| Option | Type | Description |
|
|
153
|
+
| ------------ | ----------------------------------------------------------------- | -------------------------------------------------------------------- |
|
|
154
|
+
| `auth` | `ApiAuthOption \| ApiAuthOption[]` | Auth scheme(s). Simple string (`'bearer'`) or named (`{ type: 'bearer', name: 'jwt' }`). Supports multiple simultaneous schemes. |
|
|
155
|
+
| `tags` | `string \| string[]` | OpenAPI tag(s) for the operation. |
|
|
156
|
+
| `operation` | `ApiOperationOptions` | Summary, description, operationId, deprecation flag, etc. |
|
|
157
|
+
| `summary` | `string` | Shorthand for `operation.summary`. Takes precedence when both are set. |
|
|
158
|
+
| `deprecated` | `boolean` | Shorthand to mark the endpoint as deprecated. Merged into `operation`. |
|
|
159
|
+
| `responses` | `ApiResponseOptions \| ApiResponseOptions[]` | One or more response definitions. |
|
|
160
|
+
| `headers` | `ApiHeaderOptions[]` | Expected request headers. |
|
|
161
|
+
| `params` | `ApiParamOptions \| ApiParamOptions[]` | Path parameter definitions. |
|
|
162
|
+
| `query` | `ApiQueryOptions \| ApiQueryOptions[]` | Query parameter definitions. |
|
|
163
|
+
| `body` | `ApiBodyOptions` | Request body definition. |
|
|
164
|
+
| `scopes` | `string[]` | OAuth2 scopes (only relevant when `auth` includes `'oauth2'`). |
|
|
165
|
+
| `extensions` | `Record<string, unknown>` | Custom OpenAPI extension properties (`x-*`). |
|
|
166
|
+
| `consumes` | `string \| string[]` | MIME type(s) the endpoint consumes (e.g. `'multipart/form-data'`). |
|
|
167
|
+
| `produces` | `string \| string[]` | MIME type(s) the endpoint produces (e.g. `'application/pdf'`). |
|
|
168
|
+
| `extraModels`| `Function \| Function[]` | Extra model classes for polymorphic / discriminated-union schemas. |
|
|
169
|
+
| `security` | `IApiSecurityOption \| IApiSecurityOption[]` | Generic security scheme(s) for custom API keys or non-standard auth. |
|
|
170
|
+
| `exclude` | `boolean` | Excludes this endpoint from the Swagger documentation when `true`. |
|
|
171
|
+
| `callbacks` | `ApiCallbacksOptions` | OpenAPI callback / webhook definitions. |
|
|
164
172
|
|
|
165
173
|
---
|
|
166
174
|
|
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Request & Response DTOs
|
|
3
|
+
description: Naming conventions, validation decorators, and class-transformer integration for request and response data transfer objects in Archipel NestJS applications.
|
|
4
|
+
order: 4
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Request & Response DTOs
|
|
8
|
+
|
|
9
|
+
This guide covers how to define, validate, and transform request and response DTOs in an Archipel-powered NestJS application. DTOs are the contract between your API and its consumers — they define what goes in and what comes out.
|
|
10
|
+
|
|
11
|
+
For how controllers use DTOs together with `ResponseReturn`, see the [Controller Best Practices](/guides/controller-best-practices) guide.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Naming Conventions
|
|
16
|
+
|
|
17
|
+
DTOs follow a strict naming pattern:
|
|
18
|
+
|
|
19
|
+
| Type | Pattern | Example |
|
|
20
|
+
|------|---------|---------|
|
|
21
|
+
| Create request | `Create{Resource}Request` | `CreateOrderRequest` |
|
|
22
|
+
| Update request | `Update{Resource}Request` | `UpdateProfileRequest` |
|
|
23
|
+
| Action request | `{Action}{Resource}Request` | `AuthLoginRequest`, `AuthVerifyPinRequest` |
|
|
24
|
+
| Single response | `{Resource}Response` | `UserResponse`, `OrderResponse` |
|
|
25
|
+
| List response | `{Resource}ListResponse` | `OrderListResponse` |
|
|
26
|
+
| Action response | `{Action}{Resource}Response` | `AuthLoginResponse`, `AuthRefreshResponse` |
|
|
27
|
+
| Availability / status | `{Resource}{Status}Response` | `AuthUsernameAvailabilityResponse` |
|
|
28
|
+
|
|
29
|
+
### Grouping by Feature
|
|
30
|
+
|
|
31
|
+
When a controller has many DTOs, prefix them with the feature name to keep imports clean:
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
// Auth feature DTOs
|
|
35
|
+
AuthLoginRequest
|
|
36
|
+
AuthLoginResponse
|
|
37
|
+
AuthRegisterRequest
|
|
38
|
+
AuthRefreshResponse
|
|
39
|
+
AuthChangePasswordRequest
|
|
40
|
+
AuthChangeEmailRequest
|
|
41
|
+
AuthForgotPasswordRequest
|
|
42
|
+
AuthVerifyPinRequest
|
|
43
|
+
AuthVerifyResponse
|
|
44
|
+
AuthResendVerificationRequest
|
|
45
|
+
AuthUsernameSuggestionsResponse
|
|
46
|
+
AuthUsernameAvailabilityResponse
|
|
47
|
+
AuthAvatarSuggestionResponse
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
All DTOs for a feature are exported from a `models/` barrel file:
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
// models/index.ts
|
|
54
|
+
export { AuthLoginRequest } from './AuthLoginRequest';
|
|
55
|
+
export { AuthLoginResponse } from './AuthLoginResponse';
|
|
56
|
+
export { AuthRegisterRequest } from './AuthRegisterRequest';
|
|
57
|
+
// ...
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## File Organization
|
|
63
|
+
|
|
64
|
+
**One public export per file.** Each DTO class gets its own file, named after the class.
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
src/
|
|
68
|
+
auth/
|
|
69
|
+
models/
|
|
70
|
+
AuthLoginRequest.ts
|
|
71
|
+
AuthLoginResponse.ts
|
|
72
|
+
AuthRegisterRequest.ts
|
|
73
|
+
AuthRefreshResponse.ts
|
|
74
|
+
UserResponse.ts
|
|
75
|
+
index.ts
|
|
76
|
+
AuthController.ts
|
|
77
|
+
AuthModule.ts
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## Request DTOs (Validation)
|
|
83
|
+
|
|
84
|
+
Request DTOs are classes decorated with `class-validator` and `@ApiProperty` from `@nestjs/swagger`.
|
|
85
|
+
|
|
86
|
+
### Basic Request
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
import { ApiProperty } from '@nestjs/swagger';
|
|
90
|
+
import { IsEmail, IsNotEmpty, IsString, MinLength } from 'class-validator';
|
|
91
|
+
|
|
92
|
+
export class AuthLoginRequest {
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* The user's email address or username.
|
|
96
|
+
*
|
|
97
|
+
* @public
|
|
98
|
+
*/
|
|
99
|
+
@ApiProperty({ description: 'Email address or username.' })
|
|
100
|
+
@IsNotEmpty()
|
|
101
|
+
@IsString()
|
|
102
|
+
public login!: string;
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* The user's password.
|
|
106
|
+
*
|
|
107
|
+
* @public
|
|
108
|
+
*/
|
|
109
|
+
@ApiProperty({ description: 'Account password.' })
|
|
110
|
+
@IsNotEmpty()
|
|
111
|
+
@IsString()
|
|
112
|
+
@MinLength(8)
|
|
113
|
+
public password!: string;
|
|
114
|
+
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Request with Optional Fields
|
|
119
|
+
|
|
120
|
+
```typescript
|
|
121
|
+
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
|
122
|
+
import { IsEmail, IsNotEmpty, IsOptional, IsString, ValidateNested } from 'class-validator';
|
|
123
|
+
import { Type } from 'class-transformer';
|
|
124
|
+
|
|
125
|
+
export class AuthRegisterRequest {
|
|
126
|
+
|
|
127
|
+
@ApiProperty({ description: 'Email address for the new account.' })
|
|
128
|
+
@IsNotEmpty()
|
|
129
|
+
@IsEmail()
|
|
130
|
+
public email!: string;
|
|
131
|
+
|
|
132
|
+
@ApiProperty({ description: 'Password for the new account.', minLength: 8 })
|
|
133
|
+
@IsNotEmpty()
|
|
134
|
+
@IsString()
|
|
135
|
+
@MinLength(8)
|
|
136
|
+
public password!: string;
|
|
137
|
+
|
|
138
|
+
@ApiProperty({ description: 'Account type.', example: 'personal' })
|
|
139
|
+
@IsNotEmpty()
|
|
140
|
+
@IsString()
|
|
141
|
+
public type!: string;
|
|
142
|
+
|
|
143
|
+
@ApiPropertyOptional({ description: 'Optional profile information.' })
|
|
144
|
+
@IsOptional()
|
|
145
|
+
@ValidateNested()
|
|
146
|
+
@Type(() => ProfilePayload)
|
|
147
|
+
public profile?: ProfilePayload;
|
|
148
|
+
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Nested Payloads
|
|
153
|
+
|
|
154
|
+
When a request contains nested objects, define them as separate classes with `@ValidateNested()` and `@Type()`:
|
|
155
|
+
|
|
156
|
+
```typescript
|
|
157
|
+
import { ApiPropertyOptional } from '@nestjs/swagger';
|
|
158
|
+
import { IsOptional, IsString } from 'class-validator';
|
|
159
|
+
|
|
160
|
+
export class ProfilePayload {
|
|
161
|
+
|
|
162
|
+
@ApiPropertyOptional()
|
|
163
|
+
@IsOptional()
|
|
164
|
+
@IsString()
|
|
165
|
+
public avatar?: string;
|
|
166
|
+
|
|
167
|
+
@ApiPropertyOptional()
|
|
168
|
+
@IsOptional()
|
|
169
|
+
@IsString()
|
|
170
|
+
public firstName?: string;
|
|
171
|
+
|
|
172
|
+
@ApiPropertyOptional()
|
|
173
|
+
@IsOptional()
|
|
174
|
+
@IsString()
|
|
175
|
+
public lastName?: string;
|
|
176
|
+
|
|
177
|
+
@ApiPropertyOptional()
|
|
178
|
+
@IsOptional()
|
|
179
|
+
@IsString()
|
|
180
|
+
public userName?: string;
|
|
181
|
+
|
|
182
|
+
}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
## Response DTOs (Transformation)
|
|
188
|
+
|
|
189
|
+
Response DTOs use `@ApiProperty` for documentation and `@Expose()` from `class-transformer` for serialization control.
|
|
190
|
+
|
|
191
|
+
### Basic Response
|
|
192
|
+
|
|
193
|
+
```typescript
|
|
194
|
+
import { ApiProperty } from '@nestjs/swagger';
|
|
195
|
+
import { Expose } from 'class-transformer';
|
|
196
|
+
|
|
197
|
+
export class AuthLoginResponse {
|
|
198
|
+
|
|
199
|
+
@ApiProperty({ description: 'JWT access token.' })
|
|
200
|
+
@Expose()
|
|
201
|
+
public accessToken!: string;
|
|
202
|
+
|
|
203
|
+
@ApiProperty({ description: 'Whether multi-factor authentication is required.' })
|
|
204
|
+
@Expose()
|
|
205
|
+
public mfaRequired!: boolean;
|
|
206
|
+
|
|
207
|
+
}
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### Response with Optional Fields
|
|
211
|
+
|
|
212
|
+
```typescript
|
|
213
|
+
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
|
214
|
+
import { Expose } from 'class-transformer';
|
|
215
|
+
|
|
216
|
+
export class AuthLoginResponse {
|
|
217
|
+
|
|
218
|
+
@ApiProperty({ description: 'JWT access token.' })
|
|
219
|
+
@Expose()
|
|
220
|
+
public accessToken!: string;
|
|
221
|
+
|
|
222
|
+
@ApiProperty({ description: 'Whether MFA is required.' })
|
|
223
|
+
@Expose()
|
|
224
|
+
public mfaRequired!: boolean;
|
|
225
|
+
|
|
226
|
+
@ApiProperty({ description: 'Whether email verification is required.' })
|
|
227
|
+
@Expose()
|
|
228
|
+
public verificationRequired!: boolean;
|
|
229
|
+
|
|
230
|
+
@ApiPropertyOptional({ description: 'MFA challenge identifier.' })
|
|
231
|
+
@Expose()
|
|
232
|
+
public challengeId?: string;
|
|
233
|
+
|
|
234
|
+
@ApiPropertyOptional({ description: 'Available MFA methods.', type: [String] })
|
|
235
|
+
@Expose()
|
|
236
|
+
public methods?: Array<string>;
|
|
237
|
+
|
|
238
|
+
@ApiPropertyOptional({ description: 'Preferred MFA method.' })
|
|
239
|
+
@Expose()
|
|
240
|
+
public preferredMethod?: string;
|
|
241
|
+
|
|
242
|
+
}
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
### User / Entity Response
|
|
246
|
+
|
|
247
|
+
```typescript
|
|
248
|
+
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
|
249
|
+
import { Expose } from 'class-transformer';
|
|
250
|
+
|
|
251
|
+
export class UserResponse {
|
|
252
|
+
|
|
253
|
+
@ApiProperty({ description: 'Unique user identifier.' })
|
|
254
|
+
@Expose()
|
|
255
|
+
public id!: string;
|
|
256
|
+
|
|
257
|
+
@ApiProperty({ description: 'Email address.' })
|
|
258
|
+
@Expose()
|
|
259
|
+
public email!: string;
|
|
260
|
+
|
|
261
|
+
@ApiPropertyOptional({ description: 'Display username.' })
|
|
262
|
+
@Expose()
|
|
263
|
+
public userName?: string;
|
|
264
|
+
|
|
265
|
+
@ApiPropertyOptional({ description: 'First name.' })
|
|
266
|
+
@Expose()
|
|
267
|
+
public firstName?: string;
|
|
268
|
+
|
|
269
|
+
@ApiPropertyOptional({ description: 'Last name.' })
|
|
270
|
+
@Expose()
|
|
271
|
+
public lastName?: string;
|
|
272
|
+
|
|
273
|
+
@ApiPropertyOptional({ description: 'Avatar URL or base64 image.' })
|
|
274
|
+
@Expose()
|
|
275
|
+
public avatar?: string;
|
|
276
|
+
|
|
277
|
+
@ApiProperty({ description: 'Whether the email is verified.' })
|
|
278
|
+
@Expose()
|
|
279
|
+
public isVerified!: boolean;
|
|
280
|
+
|
|
281
|
+
}
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
---
|
|
285
|
+
|
|
286
|
+
## How ResponseReturn Uses DTOs
|
|
287
|
+
|
|
288
|
+
When a controller calls `ResponseReturn.ok(AuthLoginResponse, data)`, the response pipeline:
|
|
289
|
+
|
|
290
|
+
1. Instantiates `AuthLoginResponse` via `class-transformer`'s `plainToInstance()`
|
|
291
|
+
2. Applies `@Expose()` / `@Exclude()` rules — only explicitly exposed fields appear in the output
|
|
292
|
+
3. Wraps the result in the standard envelope: `{ success, status, data, timestamp }`
|
|
293
|
+
|
|
294
|
+
This means the DTO class **controls what the client sees**. Even if the service returns extra fields, they won't leak unless the DTO exposes them.
|
|
295
|
+
|
|
296
|
+
```typescript
|
|
297
|
+
// Service returns: { id, email, password, internalFlag, ... }
|
|
298
|
+
// DTO only exposes: { id, email, userName }
|
|
299
|
+
// Client receives: { success: true, status: 200, data: { id, email, userName }, timestamp: ... }
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
---
|
|
303
|
+
|
|
304
|
+
## Validation Pipeline
|
|
305
|
+
|
|
306
|
+
Archipel applications use a global `ValidationPipe` with strict settings:
|
|
307
|
+
|
|
308
|
+
```typescript
|
|
309
|
+
app.useGlobalPipes(new ValidationPipe({
|
|
310
|
+
whitelist: true, // Strip properties not in the DTO
|
|
311
|
+
forbidNonWhitelisted: true, // Throw if unknown properties are sent
|
|
312
|
+
transform: true, // Auto-transform payloads to DTO instances
|
|
313
|
+
}));
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
This means:
|
|
317
|
+
- Properties without decorators are **silently removed** (`whitelist`)
|
|
318
|
+
- Unknown properties cause a **400 Bad Request** (`forbidNonWhitelisted`)
|
|
319
|
+
- Request bodies are automatically transformed to class instances (`transform`)
|
|
320
|
+
|
|
321
|
+
---
|
|
322
|
+
|
|
323
|
+
## Cross-Field Validation
|
|
324
|
+
|
|
325
|
+
When fields depend on each other (e.g., either `email` or `phone` is required, but not both), implement a custom class-level validator:
|
|
326
|
+
|
|
327
|
+
```typescript
|
|
328
|
+
import { ValidatorConstraint, ValidatorConstraintInterface, Validate, ValidationArguments } from 'class-validator';
|
|
329
|
+
|
|
330
|
+
@ValidatorConstraint({ name: 'eitherEmailOrPhone', async: false })
|
|
331
|
+
export class EitherEmailOrPhoneConstraint implements ValidatorConstraintInterface {
|
|
332
|
+
|
|
333
|
+
public validate(_: unknown, args: ValidationArguments): boolean {
|
|
334
|
+
const obj = args.object as { email?: string; phone?: string };
|
|
335
|
+
return Boolean(obj.email) !== Boolean(obj.phone);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
public defaultMessage(): string {
|
|
339
|
+
return 'Exactly one of email or phone must be provided.';
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
@Validate(EitherEmailOrPhoneConstraint)
|
|
345
|
+
export class ContactRequest {
|
|
346
|
+
|
|
347
|
+
@ApiPropertyOptional()
|
|
348
|
+
@IsOptional()
|
|
349
|
+
@IsEmail()
|
|
350
|
+
public email?: string;
|
|
351
|
+
|
|
352
|
+
@ApiPropertyOptional()
|
|
353
|
+
@IsOptional()
|
|
354
|
+
@IsString()
|
|
355
|
+
public phone?: string;
|
|
356
|
+
|
|
357
|
+
}
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
**Never** do cross-field validation in controllers or services — always use class-level validators so it's reusable and testable.
|
|
361
|
+
|
|
362
|
+
---
|
|
363
|
+
|
|
364
|
+
## Constants for Limits
|
|
365
|
+
|
|
366
|
+
Define and reuse constants for bounds that appear in validation and pagination:
|
|
367
|
+
|
|
368
|
+
```typescript
|
|
369
|
+
// constants.ts
|
|
370
|
+
export const MAX_PAGE_SIZE = 100;
|
|
371
|
+
export const MAX_SEARCH_LIMIT = 50;
|
|
372
|
+
export const DEFAULT_PAGE_SIZE = 20;
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
```typescript
|
|
376
|
+
import { Max, Min, IsOptional, IsInt } from 'class-validator';
|
|
377
|
+
import { MAX_PAGE_SIZE, DEFAULT_PAGE_SIZE } from '../constants';
|
|
378
|
+
|
|
379
|
+
export class PaginationQuery {
|
|
380
|
+
|
|
381
|
+
@ApiPropertyOptional({ default: DEFAULT_PAGE_SIZE, maximum: MAX_PAGE_SIZE })
|
|
382
|
+
@IsOptional()
|
|
383
|
+
@IsInt()
|
|
384
|
+
@Min(1)
|
|
385
|
+
@Max(MAX_PAGE_SIZE)
|
|
386
|
+
public limit?: number = DEFAULT_PAGE_SIZE;
|
|
387
|
+
|
|
388
|
+
@ApiPropertyOptional({ default: 0 })
|
|
389
|
+
@IsOptional()
|
|
390
|
+
@IsInt()
|
|
391
|
+
@Min(0)
|
|
392
|
+
public offset?: number = 0;
|
|
393
|
+
|
|
394
|
+
}
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
---
|
|
398
|
+
|
|
399
|
+
## Checklist
|
|
400
|
+
|
|
401
|
+
Before submitting DTOs, verify:
|
|
402
|
+
|
|
403
|
+
- [ ] One DTO class per file, named after the class
|
|
404
|
+
- [ ] Request DTOs have `class-validator` decorators on every property
|
|
405
|
+
- [ ] Response DTOs have `@Expose()` on every public property
|
|
406
|
+
- [ ] Nested objects use `@ValidateNested()` + `@Type()`
|
|
407
|
+
- [ ] All properties have `@ApiProperty` or `@ApiPropertyOptional`
|
|
408
|
+
- [ ] No `any` types — use `unknown` or specific types
|
|
409
|
+
- [ ] Cross-field validation uses class-level validators, not imperative checks
|
|
410
|
+
- [ ] Pagination limits use shared constants with `@Max()`
|
|
411
|
+
- [ ] DTOs exported from a `models/index.ts` barrel
|
|
@@ -32,7 +32,9 @@ For Redis-backed caching:
|
|
|
32
32
|
import { RedisLayeredCache } from '@breadstone/archipel-platform-caching/redis';
|
|
33
33
|
|
|
34
34
|
const cache = new RedisLayeredCache<string, IProduct>(redisClient, async (key) => productRepository.findById(key), {
|
|
35
|
-
|
|
35
|
+
url: 'redis://localhost:6379',
|
|
36
|
+
keyPrefix: 'products:',
|
|
37
|
+
ttlSeconds: 300,
|
|
36
38
|
cacheName: 'products',
|
|
37
39
|
});
|
|
38
40
|
```
|
|
@@ -72,12 +74,13 @@ Both implementations share this contract:
|
|
|
72
74
|
|
|
73
75
|
| Property | Type | Default | Description |
|
|
74
76
|
| ---------------------- | ----------------------- | ---------------- | ------------------------------------------------------- |
|
|
75
|
-
| `
|
|
76
|
-
| `
|
|
77
|
-
| `
|
|
78
|
-
| `cacheName` | `string` | `'redis'` | Label used in metrics and logging. |
|
|
77
|
+
| `url` | `string` | — | Redis connection URL (e.g. `redis://localhost:6379`). |
|
|
78
|
+
| `keyPrefix` | `string` | — | Key prefix applied to all Redis keys. |
|
|
79
|
+
| `ttlSeconds` | `number` | ∞ | Time-to-live in seconds. |
|
|
79
80
|
| `serialize` | `(value) => string` | `JSON.stringify` | Custom serializer for cache values. |
|
|
80
81
|
| `deserialize` | `(raw) => value` | `JSON.parse` | Custom deserializer for cache entries. |
|
|
82
|
+
| `metricsRecorder` | `ICacheMetricsRecorder` | Noop | Pluggable metrics hook. |
|
|
83
|
+
| `cacheName` | `string` | `'redis'` | Label used in metrics and logging. |
|
|
81
84
|
|
|
82
85
|
---
|
|
83
86
|
|
|
@@ -15,12 +15,10 @@ Cryptographic building blocks for NestJS applications: bcrypt password hashing,
|
|
|
15
15
|
## Quick Start
|
|
16
16
|
|
|
17
17
|
```typescript
|
|
18
|
-
import {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
OTP_SERVICE_TOKEN,
|
|
23
|
-
} from '@breadstone/archipel-platform-cryptography';
|
|
18
|
+
import { BcryptService, CryptoService } from '@breadstone/archipel-platform-cryptography';
|
|
19
|
+
|
|
20
|
+
// OTP is a tree-shakable sub-export
|
|
21
|
+
import { OtpService, OTP_SERVICE_TOKEN } from '@breadstone/archipel-platform-cryptography/otp';
|
|
24
22
|
```
|
|
25
23
|
|
|
26
24
|
---
|
|
@@ -83,7 +81,7 @@ TOTP enrollment and verification backed by otplib v13, injected via the `OTP_SER
|
|
|
83
81
|
|
|
84
82
|
```typescript
|
|
85
83
|
import { Module } from '@nestjs/common';
|
|
86
|
-
import { OtpService, OTP_SERVICE_TOKEN } from '@breadstone/archipel-platform-cryptography';
|
|
84
|
+
import { OtpService, OTP_SERVICE_TOKEN } from '@breadstone/archipel-platform-cryptography/otp';
|
|
87
85
|
|
|
88
86
|
@Module({
|
|
89
87
|
providers: [{ provide: OTP_SERVICE_TOKEN, useClass: OtpService }],
|
|
@@ -96,7 +94,7 @@ export class SecurityModule {}
|
|
|
96
94
|
|
|
97
95
|
```typescript
|
|
98
96
|
import { Inject, Injectable } from '@nestjs/common';
|
|
99
|
-
import { OTP_SERVICE_TOKEN, IOtpService } from '@breadstone/archipel-platform-cryptography';
|
|
97
|
+
import { OTP_SERVICE_TOKEN, IOtpService } from '@breadstone/archipel-platform-cryptography/otp';
|
|
100
98
|
|
|
101
99
|
@Injectable()
|
|
102
100
|
export class MfaService {
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: platform-logging
|
|
3
|
-
description:
|
|
3
|
+
description: Configurable logging module for NestJS with log-level management.
|
|
4
4
|
order: 6
|
|
5
|
-
tags: [logging,
|
|
5
|
+
tags: [logging, log-level, nestjs]
|
|
6
6
|
package: '@breadstone/archipel-platform-logging'
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
# platform-logging
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
Configurable logging module for NestJS applications with application-wide log-level management. Wraps the built-in NestJS `Logger` and configures it via the central `ConfigModule`.
|
|
12
12
|
|
|
13
13
|
**Package:** `@breadstone/archipel-platform-logging`
|
|
14
14
|
|
|
@@ -24,28 +24,24 @@ import { LoggerModule } from '@breadstone/archipel-platform-logging';
|
|
|
24
24
|
export class AppModule {}
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
+
The module is `@Global()` — once imported, `Logger` is available for injection across the entire application.
|
|
28
|
+
|
|
27
29
|
---
|
|
28
30
|
|
|
29
|
-
##
|
|
31
|
+
## Using the Logger
|
|
30
32
|
|
|
31
|
-
|
|
33
|
+
Inject the standard NestJS `Logger` provided by `LoggerModule`:
|
|
32
34
|
|
|
33
35
|
```typescript
|
|
34
|
-
import {
|
|
36
|
+
import { Injectable, Logger } from '@nestjs/common';
|
|
35
37
|
|
|
36
38
|
@Injectable()
|
|
37
39
|
export class PaymentService {
|
|
38
|
-
constructor(private readonly
|
|
40
|
+
constructor(private readonly _logger: Logger) {}
|
|
39
41
|
|
|
40
42
|
public async processPayment(orderId: string): Promise<void> {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
} catch (error) {
|
|
44
|
-
this._analytics.captureException(error, {
|
|
45
|
-
tags: { module: 'payments', orderId },
|
|
46
|
-
});
|
|
47
|
-
throw error;
|
|
48
|
-
}
|
|
43
|
+
this._logger.log(`Processing payment for order ${orderId}`);
|
|
44
|
+
// ...
|
|
49
45
|
}
|
|
50
46
|
}
|
|
51
47
|
```
|
|
@@ -54,39 +50,15 @@ export class PaymentService {
|
|
|
54
50
|
|
|
55
51
|
## Environment Variables
|
|
56
52
|
|
|
57
|
-
| Variable
|
|
58
|
-
|
|
|
59
|
-
| `
|
|
60
|
-
| `SENTRY_ENVIRONMENT` | Environment label (e.g. `'production'`, `'staging'`) | No |
|
|
61
|
-
| `SENTRY_TRACES_SAMPLE_RATE` | Sampling rate for performance traces (0.0–1.0) | No |
|
|
62
|
-
|
|
63
|
-
---
|
|
64
|
-
|
|
65
|
-
## Real-World Example: Global Error Filter
|
|
66
|
-
|
|
67
|
-
```typescript
|
|
68
|
-
import { Catch, type ExceptionFilter, type ArgumentsHost } from '@nestjs/common';
|
|
69
|
-
import { AnalyticsService } from '@breadstone/archipel-platform-logging';
|
|
70
|
-
|
|
71
|
-
@Catch()
|
|
72
|
-
export class GlobalExceptionFilter implements ExceptionFilter {
|
|
73
|
-
constructor(private readonly _analytics: AnalyticsService) {}
|
|
74
|
-
|
|
75
|
-
public catch(exception: unknown, host: ArgumentsHost): void {
|
|
76
|
-
this._analytics.captureException(exception);
|
|
77
|
-
|
|
78
|
-
const ctx = host.switchToHttp();
|
|
79
|
-
const response = ctx.getResponse();
|
|
80
|
-
response.status(500).json({ error: 'Internal Server Error' });
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
```
|
|
53
|
+
| Variable | Description | Required | Default |
|
|
54
|
+
| --------------- | -------------------------------------------------------------- | -------- | ------- |
|
|
55
|
+
| `APP_LOG_LEVEL` | Application log level (`'debug'`, `'info'`, `'warn'`, `'error'`) | No | `warn` |
|
|
84
56
|
|
|
85
57
|
---
|
|
86
58
|
|
|
87
59
|
## Exports Summary
|
|
88
60
|
|
|
89
|
-
| Export
|
|
90
|
-
|
|
|
91
|
-
| `LoggerModule`
|
|
92
|
-
| `
|
|
61
|
+
| Export | Type | Description |
|
|
62
|
+
| -------------- | ------------- | -------------------------------------------- |
|
|
63
|
+
| `LoggerModule` | NestJS Module | Global logging module with log-level control |
|
|
64
|
+
| `Logger` | Provider | NestJS Logger configured with `APP_LOG_LEVEL`|
|