@breadstone/archipel-mcp 0.0.21 → 0.0.22
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/cryptography-and-otp.md +2 -2
- package/data/guides/database-setup.md +4 -3
- package/data/guides/email-templates.md +3 -3
- package/data/guides/getting-started.md +6 -6
- package/data/guides/resource-management.md +9 -9
- package/data/packages/platform-blob-storage/api/Variable.AWS_S3_PROVIDER_OPTIONS.md +1 -1
- package/data/packages/platform-blob-storage/api/Variable.AZURE_BLOB_PROVIDER_OPTIONS.md +1 -1
- package/data/packages/platform-blob-storage/api/Variable.BLOB_PROVIDER.md +1 -1
- package/data/packages/platform-blob-storage/api/Variable.VERCEL_BLOB_PROVIDER_OPTIONS.md +1 -1
- package/data/packages/platform-core/api/Class.ErrorTemplateService.md +1 -1
- package/data/packages/platform-core/api/Class.HostService.md +1 -1
- package/data/packages/platform-core/api/Variable.ID_GENERATOR_TOKEN.md +1 -1
- package/data/packages/platform-core/api/index.md +0 -27
- package/data/packages/platform-core/index.md +7 -4
- package/data/packages/platform-cryptography/api/Class.BcryptService.md +4 -4
- package/data/packages/platform-cryptography/api/Class.OtpService.md +6 -6
- package/data/packages/platform-cryptography/api/Variable.BCRYPT_OPTIONS.md +2 -2
- package/data/packages/platform-cryptography/api/Variable.MAX_BCRYPT_PASSWORD_BYTES.md +1 -1
- package/data/packages/platform-cryptography/api/Variable.MIN_BCRYPT_ROUNDS.md +1 -1
- package/data/packages/platform-cryptography/api/Variable.OTP_OPTIONS.md +2 -2
- package/data/packages/platform-cryptography/api/Variable.OTP_SERVICE_TOKEN.md +2 -2
- package/data/packages/platform-cryptography/api/Variable.TOTP_EPOCH_TOLERANCE.md +1 -1
- package/data/packages/platform-cryptography/api/index.md +3 -3
- package/data/packages/platform-documents/api/Class.BaseDocumentRenderer.md +32 -1
- package/data/packages/platform-documents/api/Class.DocumentEngine.md +4 -4
- package/data/packages/platform-documents/api/Class.DocumentModule.md +2 -2
- package/data/packages/platform-documents/api/Class.DocxDocumentRenderer2.md +333 -0
- package/data/packages/platform-documents/api/Class.PdfDocumentRenderer.md +355 -0
- package/data/packages/platform-documents/api/Interface.IDocumentModuleOptions.md +31 -5
- package/data/packages/platform-documents/api/Variable.DOCUMENT_MODULE_OPTIONS.md +1 -1
- package/data/packages/platform-documents/api/Variable.DOCUMENT_PARSER_TOKEN.md +1 -1
- package/data/packages/platform-documents/api/Variable.DOCUMENT_RENDERER_TOKEN.md +1 -1
- package/data/packages/platform-documents/api/Variable.IMAGE_PROCESSOR_TOKEN.md +1 -1
- package/data/packages/platform-documents/api/index.md +2 -0
- package/data/packages/platform-esigning/api/Class.EsigningClientPort.md +1 -1
- package/data/packages/platform-health/index.md +128 -0
- package/data/packages/platform-mailing/api/Class.DeliveryStrategyBase.md +0 -1
- package/data/packages/platform-mailing/api/Class.MailModule.md +1 -1
- package/data/packages/platform-mailing/api/Class.TemplateFetchStrategyBase.md +0 -5
- package/data/packages/platform-mailing/api/Variable.SMTP_CONFIG_ENTRIES.md +14 -0
- package/data/packages/platform-mailing/api/Variable.SMTP_HOST.md +14 -0
- package/data/packages/platform-mailing/api/Variable.SMTP_PASSWORD.md +14 -0
- package/data/packages/platform-mailing/api/Variable.SMTP_PORT.md +14 -0
- package/data/packages/platform-mailing/api/Variable.SMTP_SECURE.md +14 -0
- package/data/packages/platform-mailing/api/Variable.SMTP_USER.md +14 -0
- package/data/packages/platform-mailing/api/index.md +6 -3
- package/data/packages/platform-mapping/index.md +121 -0
- package/data/packages/platform-mcp/api/Variable.MCP_MODULE_OPTIONS.md +1 -1
- package/data/packages/platform-openapi/api/Class.SwaggerMultiDocumentService.md +4 -4
- package/data/packages/platform-resources/index.md +135 -0
- package/data/packages/platform-telemetry/api/Variable.TELEMETRY_ENABLED.md +1 -1
- package/data/packages/platform-telemetry/api/Variable.TELEMETRY_FACADE.md +1 -1
- package/data/packages/platform-telemetry/api/Variable.TELEMETRY_OPTIONS.md +1 -1
- package/{src/knowledge/configPattern.js → data/patterns/config-pattern.md} +44 -47
- package/{src/knowledge/dtoPattern.js → data/patterns/dto-pattern.md} +58 -61
- package/{src/knowledge/errorHandlingPattern.js → data/patterns/error-handling-pattern.md} +35 -38
- package/{src/knowledge/guardPattern.js → data/patterns/guard-pattern.md} +35 -38
- package/{src/knowledge/mappingPattern.js → data/patterns/mapping-pattern.md} +43 -144
- package/data/patterns/module-pattern.md +182 -0
- package/data/patterns/query-pattern.md +137 -0
- package/data/patterns/repository-pattern.md +208 -0
- package/{src/knowledge/testingPattern.js → data/patterns/testing-pattern.md} +37 -40
- package/package.json +1 -1
- package/src/PatternsLoader.d.ts +12 -0
- package/src/PatternsLoader.js +65 -0
- package/src/generators/mappingPatternGenerator.d.ts +5 -0
- package/src/generators/mappingPatternGenerator.js +107 -0
- package/src/generators/modulePatternGenerator.d.ts +5 -0
- package/src/generators/modulePatternGenerator.js +107 -0
- package/src/generators/queryPatternGenerator.d.ts +4 -0
- package/src/generators/queryPatternGenerator.js +83 -0
- package/src/generators/repositoryPatternGenerator.d.ts +5 -0
- package/src/generators/repositoryPatternGenerator.js +165 -0
- package/src/main.js +15 -9
- package/src/models/IPatternDoc.d.ts +15 -0
- package/src/models/IPatternDoc.js +3 -0
- package/src/tools/registerGetConfigPatternTool.d.ts +2 -1
- package/src/tools/registerGetConfigPatternTool.js +4 -3
- package/src/tools/registerGetDtoPatternTool.d.ts +2 -1
- package/src/tools/registerGetDtoPatternTool.js +4 -3
- package/src/tools/registerGetErrorHandlingPatternTool.d.ts +2 -1
- package/src/tools/registerGetErrorHandlingPatternTool.js +4 -3
- package/src/tools/registerGetGuardPatternTool.d.ts +2 -1
- package/src/tools/registerGetGuardPatternTool.js +4 -3
- package/src/tools/registerGetMappingPatternTool.d.ts +2 -1
- package/src/tools/registerGetMappingPatternTool.js +5 -4
- package/src/tools/registerGetModulePatternTool.d.ts +2 -1
- package/src/tools/registerGetModulePatternTool.js +5 -4
- package/src/tools/registerGetQueryPatternTool.d.ts +2 -1
- package/src/tools/registerGetQueryPatternTool.js +5 -4
- package/src/tools/registerGetRepositoryPatternTool.d.ts +2 -1
- package/src/tools/registerGetRepositoryPatternTool.js +5 -4
- package/src/tools/registerGetTestingPatternTool.d.ts +2 -1
- package/src/tools/registerGetTestingPatternTool.js +4 -3
- package/data/packages/platform-core/api/Class.BlobResourceStrategy.md +0 -195
- package/data/packages/platform-core/api/Class.EmbeddedResourceStrategy.md +0 -215
- package/data/packages/platform-core/api/Class.FileResourceStrategy.md +0 -192
- package/data/packages/platform-core/api/Class.HealthModule.md +0 -42
- package/data/packages/platform-core/api/Class.HealthOrchestrator.md +0 -64
- package/data/packages/platform-core/api/Class.MappingBuilder.md +0 -110
- package/data/packages/platform-core/api/Class.MappingModule.md +0 -46
- package/data/packages/platform-core/api/Class.MappingNotRegisteredError.md +0 -56
- package/data/packages/platform-core/api/Class.MappingProfileBase.md +0 -52
- package/data/packages/platform-core/api/Class.MappingService.md +0 -284
- package/data/packages/platform-core/api/Class.ResourceManager.md +0 -565
- package/data/packages/platform-core/api/Class.ResourceModule.md +0 -46
- package/data/packages/platform-core/api/Class.TypeMappingNotRegisteredError.md +0 -57
- package/data/packages/platform-core/api/Function.createMappingKey.md +0 -39
- package/data/packages/platform-core/api/Interface.IBlobResourceStrategyConfig.md +0 -28
- package/data/packages/platform-core/api/Interface.IBlobServiceAdapter.md +0 -40
- package/data/packages/platform-core/api/Interface.IFileResourceStrategyConfig.md +0 -72
- package/data/packages/platform-core/api/Interface.IHealthCheckResult.md +0 -46
- package/data/packages/platform-core/api/Interface.IHealthIndicator.md +0 -41
- package/data/packages/platform-core/api/Interface.IMappingBuilder.md +0 -76
- package/data/packages/platform-core/api/Interface.IMappingKey.md +0 -58
- package/data/packages/platform-core/api/Interface.IMappingProfile.md +0 -32
- package/data/packages/platform-core/api/Interface.IResourceManagerConfig.md +0 -89
- package/data/packages/platform-core/api/Interface.IResourceMetadata.md +0 -94
- package/data/packages/platform-core/api/Interface.IResourceResult.md +0 -34
- package/data/packages/platform-core/api/Interface.IResourceStrategy.md +0 -134
- package/data/packages/platform-core/api/Variable.HEALTH_INDICATORS_TOKEN.md +0 -14
- package/data/packages/platform-mailing/api/Class.BlobTemplateFetchStrategy.md +0 -60
- package/data/packages/platform-mailing/api/Class.FileTemplateFetchStrategy.md +0 -58
- package/data/packages/platform-mailing/api/Class.LogDeliveryStrategy.md +0 -71
- package/src/knowledge/configPattern.d.ts +0 -5
- package/src/knowledge/dtoPattern.d.ts +0 -5
- package/src/knowledge/errorHandlingPattern.d.ts +0 -5
- package/src/knowledge/guardPattern.d.ts +0 -5
- package/src/knowledge/mappingPattern.d.ts +0 -6
- package/src/knowledge/modulePattern.d.ts +0 -6
- package/src/knowledge/modulePattern.js +0 -283
- package/src/knowledge/queryPattern.d.ts +0 -6
- package/src/knowledge/queryPattern.js +0 -215
- package/src/knowledge/repositoryPattern.d.ts +0 -6
- package/src/knowledge/repositoryPattern.js +0 -367
- package/src/knowledge/testingPattern.d.ts +0 -5
|
@@ -1,18 +1,17 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
exports.MAPPING_PATTERN_KNOWLEDGE = `# Archipel Mapping Pattern
|
|
1
|
+
---
|
|
2
|
+
title: Mapping Pattern
|
|
3
|
+
description: Branded type-safe mapping keys, MappingProfileBase, MappingService, and MappingModule registration.
|
|
4
|
+
order: 5
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Archipel Mapping Pattern
|
|
9
8
|
|
|
10
9
|
## Overview
|
|
11
10
|
|
|
12
11
|
In Archipel, all object-to-object mapping goes through a central **MappingService** with
|
|
13
12
|
**branded, type-safe mapping keys**. This guarantees compile-time type safety: the key
|
|
14
|
-
encodes both the input and output type, so calling
|
|
15
|
-
statically checked — the compiler verifies that
|
|
13
|
+
encodes both the input and output type, so calling `mappingService.map(KEY, input)` is
|
|
14
|
+
statically checked — the compiler verifies that `input` matches the key's input type and
|
|
16
15
|
infers the return type automatically.
|
|
17
16
|
|
|
18
17
|
**Rule:** Services MUST NOT inline-create response objects. They return entities.
|
|
@@ -22,32 +21,32 @@ Controllers use the MappingService to convert entities to response DTOs.
|
|
|
22
21
|
|
|
23
22
|
### IMappingKey<TInput, TOutput>
|
|
24
23
|
|
|
25
|
-
|
|
24
|
+
```typescript
|
|
26
25
|
export interface IMappingKey<TInput, TOutput> {
|
|
27
26
|
readonly _brand: 'MappingKey';
|
|
28
27
|
readonly _input: TInput;
|
|
29
28
|
readonly _output: TOutput;
|
|
30
29
|
readonly key: string;
|
|
31
30
|
}
|
|
32
|
-
|
|
31
|
+
```
|
|
33
32
|
|
|
34
|
-
The
|
|
33
|
+
The `_input` and `_output` fields are phantom types — they exist only for compile-time
|
|
35
34
|
type encoding and are never accessed at runtime.
|
|
36
35
|
|
|
37
36
|
### createMappingKey<TInput, TOutput>
|
|
38
37
|
|
|
39
|
-
|
|
40
|
-
import { createMappingKey } from '@breadstone/archipel-platform-
|
|
38
|
+
```typescript
|
|
39
|
+
import { createMappingKey } from '@breadstone/archipel-platform-mapping';
|
|
41
40
|
|
|
42
41
|
export const PRODUCT_MAPPING_KEY = createMappingKey<IProductMappingInput, ProductResponse>('food.product');
|
|
43
|
-
|
|
42
|
+
```
|
|
44
43
|
|
|
45
|
-
The string argument
|
|
46
|
-
|
|
44
|
+
The string argument `'food.product'` is the unique registry key. Convention:
|
|
45
|
+
`<domain>.<entity>` in lowercase dot-notation.
|
|
47
46
|
|
|
48
47
|
## MappingService API
|
|
49
48
|
|
|
50
|
-
|
|
49
|
+
```typescript
|
|
51
50
|
// Key-based mapping (preferred)
|
|
52
51
|
const response = mappingService.map(PRODUCT_MAPPING_KEY, input);
|
|
53
52
|
// → TOutput is inferred from the key, TInput is validated
|
|
@@ -58,7 +57,7 @@ const maybeResponse = mappingService.tryMap(PRODUCT_MAPPING_KEY, input);
|
|
|
58
57
|
// Type-based mapping (alternative)
|
|
59
58
|
const response = mappingService.mapTypes(sourceInstance, DestinationClass);
|
|
60
59
|
const responses = mappingService.mapTypesArray(sourceArray, DestinationClass);
|
|
61
|
-
|
|
60
|
+
```
|
|
62
61
|
|
|
63
62
|
## How to Create a Mapping
|
|
64
63
|
|
|
@@ -67,7 +66,7 @@ const responses = mappingService.mapTypesArray(sourceArray, DestinationClass);
|
|
|
67
66
|
The input interface represents the shape of data coming from the repository query.
|
|
68
67
|
Property names MUST match the Prisma select shape exactly.
|
|
69
68
|
|
|
70
|
-
|
|
69
|
+
```typescript
|
|
71
70
|
// models/mapping-inputs/IProductMappingInput.ts
|
|
72
71
|
export interface IProductMappingInput {
|
|
73
72
|
readonly id: string;
|
|
@@ -76,28 +75,28 @@ export interface IProductMappingInput {
|
|
|
76
75
|
readonly categoryId: string;
|
|
77
76
|
readonly createdAt: Date;
|
|
78
77
|
}
|
|
79
|
-
|
|
78
|
+
```
|
|
80
79
|
|
|
81
80
|
### Step 2: Create a branded mapping key
|
|
82
81
|
|
|
83
82
|
Each key is an **exported constant** — never inline magic strings.
|
|
84
83
|
|
|
85
|
-
|
|
84
|
+
```typescript
|
|
86
85
|
// constants/MappingKeys.ts
|
|
87
|
-
import { createMappingKey } from '@breadstone/archipel-platform-
|
|
86
|
+
import { createMappingKey } from '@breadstone/archipel-platform-mapping';
|
|
88
87
|
import { IProductMappingInput } from '../models/mapping-inputs/IProductMappingInput';
|
|
89
88
|
import { ProductResponse } from '../dtos/ProductResponse';
|
|
90
89
|
|
|
91
90
|
export const PRODUCT_MAPPING_KEY = createMappingKey<IProductMappingInput, ProductResponse>('food.product');
|
|
92
|
-
|
|
91
|
+
```
|
|
93
92
|
|
|
94
93
|
### Step 3: Create a mapping profile
|
|
95
94
|
|
|
96
|
-
A profile extends
|
|
95
|
+
A profile extends `MappingProfileBase` and registers one or more mappings via the builder.
|
|
97
96
|
|
|
98
|
-
|
|
97
|
+
```typescript
|
|
99
98
|
// mappers/ProductMappingProfile.ts
|
|
100
|
-
import { MappingProfileBase, IMappingBuilder } from '@breadstone/archipel-platform-
|
|
99
|
+
import { MappingProfileBase, IMappingBuilder } from '@breadstone/archipel-platform-mapping';
|
|
101
100
|
import { PRODUCT_MAPPING_KEY } from '../constants/MappingKeys';
|
|
102
101
|
import { IProductMappingInput } from '../models/mapping-inputs/IProductMappingInput';
|
|
103
102
|
import { ProductResponse } from '../dtos/ProductResponse';
|
|
@@ -122,12 +121,12 @@ export class ProductMappingProfile extends MappingProfileBase {
|
|
|
122
121
|
// #endregion
|
|
123
122
|
|
|
124
123
|
}
|
|
125
|
-
|
|
124
|
+
```
|
|
126
125
|
|
|
127
126
|
### Step 4: Register the profile in the module
|
|
128
127
|
|
|
129
|
-
|
|
130
|
-
import { MappingModule } from '@breadstone/archipel-platform-
|
|
128
|
+
```typescript
|
|
129
|
+
import { MappingModule } from '@breadstone/archipel-platform-mapping';
|
|
131
130
|
import { ProductMappingProfile } from './mappers/ProductMappingProfile';
|
|
132
131
|
|
|
133
132
|
@Module({
|
|
@@ -136,11 +135,11 @@ import { ProductMappingProfile } from './mappers/ProductMappingProfile';
|
|
|
136
135
|
],
|
|
137
136
|
})
|
|
138
137
|
export class ProductModule {}
|
|
139
|
-
|
|
138
|
+
```
|
|
140
139
|
|
|
141
140
|
### Step 5: Use in controller (NOT in service)
|
|
142
141
|
|
|
143
|
-
|
|
142
|
+
```typescript
|
|
144
143
|
@Controller('/products')
|
|
145
144
|
export class ProductController {
|
|
146
145
|
|
|
@@ -179,7 +178,7 @@ export class ProductController {
|
|
|
179
178
|
// #endregion
|
|
180
179
|
|
|
181
180
|
}
|
|
182
|
-
|
|
181
|
+
```
|
|
183
182
|
|
|
184
183
|
## MappingBuilder Methods
|
|
185
184
|
|
|
@@ -187,29 +186,29 @@ The builder supports two registration flavors:
|
|
|
187
186
|
|
|
188
187
|
### createKeyedMap (preferred)
|
|
189
188
|
|
|
190
|
-
|
|
189
|
+
```typescript
|
|
191
190
|
builder.createKeyedMap(SOME_MAPPING_KEY, (input: TInput) => {
|
|
192
191
|
const output = new TOutput();
|
|
193
192
|
// ... map fields
|
|
194
193
|
return output;
|
|
195
194
|
});
|
|
196
|
-
|
|
195
|
+
```
|
|
197
196
|
|
|
198
197
|
### createMap (type-based)
|
|
199
198
|
|
|
200
|
-
|
|
199
|
+
```typescript
|
|
201
200
|
builder.createMap(SourceClass, DestinationClass, (source: SourceClass) => {
|
|
202
201
|
const dest = new DestinationClass();
|
|
203
202
|
// ... map fields
|
|
204
203
|
return dest;
|
|
205
204
|
});
|
|
206
|
-
|
|
205
|
+
```
|
|
207
206
|
|
|
208
207
|
## Error Handling
|
|
209
208
|
|
|
210
|
-
-
|
|
211
|
-
-
|
|
212
|
-
- Both extend
|
|
209
|
+
- `MappingNotRegisteredError` — thrown when a keyed mapping is not found
|
|
210
|
+
- `TypeMappingNotRegisteredError` — thrown when a type-based mapping is not found
|
|
211
|
+
- Both extend `MappingError` with a machine-readable `code` field
|
|
213
212
|
|
|
214
213
|
## Rules
|
|
215
214
|
|
|
@@ -217,106 +216,6 @@ builder.createMap(SourceClass, DestinationClass, (source: SourceClass) => {
|
|
|
217
216
|
2. **Services return entities, controllers map to responses** — services MUST NOT create response objects.
|
|
218
217
|
3. **One mapping key per entity→response pair** — use descriptive dot notation for the key string.
|
|
219
218
|
4. **Input interfaces match Prisma select shape** — property names must align exactly.
|
|
220
|
-
5. **Profiles extend MappingProfileBase** and are registered via
|
|
221
|
-
6. **Use
|
|
222
|
-
7. **Mapping functions instantiate response classes** — use
|
|
223
|
-
`;
|
|
224
|
-
const MAPPING_PATTERN_WITH_CONTEXT = (entityName, fields, responseName) => `
|
|
225
|
-
## Example: Mapping for "${entityName}" → "${responseName}"
|
|
226
|
-
|
|
227
|
-
Given an entity \`${entityName}\` with fields: ${fields.map((f) => `\`${f}\``).join(', ')}
|
|
228
|
-
|
|
229
|
-
### 1. Input Interface
|
|
230
|
-
|
|
231
|
-
\`\`\`typescript
|
|
232
|
-
// models/mapping-inputs/I${entityName}MappingInput.ts
|
|
233
|
-
|
|
234
|
-
export interface I${entityName}MappingInput {
|
|
235
|
-
${fields.map((f) => ` readonly ${f}: ${inferMappingFieldType(f)};`).join('\n')}
|
|
236
|
-
}
|
|
237
|
-
\`\`\`
|
|
238
|
-
|
|
239
|
-
### 2. Mapping Key
|
|
240
|
-
|
|
241
|
-
\`\`\`typescript
|
|
242
|
-
// constants/MappingKeys.ts
|
|
243
|
-
import { createMappingKey } from '@breadstone/archipel-platform-core';
|
|
244
|
-
import { I${entityName}MappingInput } from '../models/mapping-inputs/I${entityName}MappingInput';
|
|
245
|
-
import { ${responseName} } from '../dtos/${responseName}';
|
|
246
|
-
|
|
247
|
-
export const ${toConstantCase(entityName)}_MAPPING_KEY = createMappingKey<I${entityName}MappingInput, ${responseName}>('${toDotNotation(entityName)}');
|
|
248
|
-
\`\`\`
|
|
249
|
-
|
|
250
|
-
### 3. Mapping Profile
|
|
251
|
-
|
|
252
|
-
\`\`\`typescript
|
|
253
|
-
// mappers/${entityName}MappingProfile.ts
|
|
254
|
-
import { MappingProfileBase, IMappingBuilder } from '@breadstone/archipel-platform-core';
|
|
255
|
-
import { ${toConstantCase(entityName)}_MAPPING_KEY } from '../constants/MappingKeys';
|
|
256
|
-
import { I${entityName}MappingInput } from '../models/mapping-inputs/I${entityName}MappingInput';
|
|
257
|
-
import { ${responseName} } from '../dtos/${responseName}';
|
|
258
|
-
|
|
259
|
-
export class ${entityName}MappingProfile extends MappingProfileBase {
|
|
260
|
-
|
|
261
|
-
// #region Methods
|
|
262
|
-
|
|
263
|
-
public configure(builder: IMappingBuilder): void {
|
|
264
|
-
builder.createKeyedMap(${toConstantCase(entityName)}_MAPPING_KEY, (input: I${entityName}MappingInput) => {
|
|
265
|
-
const response = new ${responseName}();
|
|
266
|
-
${fields.map((f) => ` response.${f} = input.${f};`).join('\n')}
|
|
267
|
-
|
|
268
|
-
return response;
|
|
269
|
-
});
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
// #endregion
|
|
273
|
-
|
|
274
|
-
}
|
|
275
|
-
\`\`\`
|
|
276
|
-
|
|
277
|
-
### 4. Module Registration
|
|
278
|
-
|
|
279
|
-
\`\`\`typescript
|
|
280
|
-
import { MappingModule } from '@breadstone/archipel-platform-core';
|
|
281
|
-
import { ${entityName}MappingProfile } from './mappers/${entityName}MappingProfile';
|
|
282
|
-
|
|
283
|
-
@Module({
|
|
284
|
-
imports: [
|
|
285
|
-
MappingModule.withProfiles([${entityName}MappingProfile]),
|
|
286
|
-
],
|
|
287
|
-
})
|
|
288
|
-
export class ${entityName}Module {}
|
|
289
|
-
\`\`\`
|
|
290
|
-
|
|
291
|
-
### 5. Controller Usage
|
|
292
|
-
|
|
293
|
-
\`\`\`typescript
|
|
294
|
-
// In a controller method:
|
|
295
|
-
const entity = await this._${lowerFirst(entityName)}Service.findById(id);
|
|
296
|
-
const response = this._mappingService.map(${toConstantCase(entityName)}_MAPPING_KEY, entity);
|
|
297
|
-
\`\`\`
|
|
298
|
-
`;
|
|
299
|
-
exports.MAPPING_PATTERN_WITH_CONTEXT = MAPPING_PATTERN_WITH_CONTEXT;
|
|
300
|
-
function inferMappingFieldType(fieldName) {
|
|
301
|
-
if (fieldName === 'id')
|
|
302
|
-
return 'string';
|
|
303
|
-
if (fieldName.endsWith('Id'))
|
|
304
|
-
return 'string';
|
|
305
|
-
if (fieldName.endsWith('At') || fieldName === 'createdAt' || fieldName === 'updatedAt')
|
|
306
|
-
return 'Date';
|
|
307
|
-
if (fieldName.startsWith('is') || fieldName.startsWith('has'))
|
|
308
|
-
return 'boolean';
|
|
309
|
-
if (fieldName === 'count' || fieldName === 'quantity' || fieldName === 'amount' || fieldName === 'price')
|
|
310
|
-
return 'number';
|
|
311
|
-
return 'string';
|
|
312
|
-
}
|
|
313
|
-
function toConstantCase(name) {
|
|
314
|
-
return name.replace(/([a-z])([A-Z])/g, '$1_$2').toUpperCase();
|
|
315
|
-
}
|
|
316
|
-
function toDotNotation(name) {
|
|
317
|
-
return name.replace(/([a-z])([A-Z])/g, '$1.$2').toLowerCase();
|
|
318
|
-
}
|
|
319
|
-
function lowerFirst(str) {
|
|
320
|
-
return str.charAt(0).toLowerCase() + str.slice(1);
|
|
321
|
-
}
|
|
322
|
-
//# sourceMappingURL=mappingPattern.js.map
|
|
219
|
+
5. **Profiles extend MappingProfileBase** and are registered via `MappingModule.withProfiles()`.
|
|
220
|
+
6. **Use `createKeyedMap` over `createMap`** — branded keys provide better type safety.
|
|
221
|
+
7. **Mapping functions instantiate response classes** — use `new ResponseClass()` and assign fields.
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Dynamic Module Pattern
|
|
3
|
+
description: NestJS dynamic modules with register(), port/adapter dependency inversion, and ConfigModule integration.
|
|
4
|
+
order: 6
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Archipel Dynamic Module Pattern
|
|
8
|
+
|
|
9
|
+
## Overview
|
|
10
|
+
|
|
11
|
+
Archipel platform libraries use **NestJS dynamic modules** with a `register()` factory
|
|
12
|
+
method and **port/adapter dependency inversion**. Each library defines abstract port
|
|
13
|
+
classes. The consuming application provides concrete implementations at registration time.
|
|
14
|
+
|
|
15
|
+
This pattern keeps platform libraries free of product-specific logic while allowing
|
|
16
|
+
full customization through dependency injection.
|
|
17
|
+
|
|
18
|
+
## Pattern Structure
|
|
19
|
+
|
|
20
|
+
### 1. Abstract Port (Contract)
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
// contracts/FeatureAccessPort.ts
|
|
24
|
+
|
|
25
|
+
export abstract class FeatureAccessPort {
|
|
26
|
+
|
|
27
|
+
public abstract checkAccess(userId: string, featureKey: string): Promise<IFeatureAccessResult>;
|
|
28
|
+
|
|
29
|
+
public abstract recordUsage(userId: string, featureKey: string): Promise<void>;
|
|
30
|
+
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Ports are **abstract classes** (not interfaces) so they can serve as NestJS injection tokens.
|
|
35
|
+
|
|
36
|
+
### 2. Options Interface
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
// interfaces/IPaymentModuleOptions.ts
|
|
40
|
+
|
|
41
|
+
export interface IPaymentModuleOptions {
|
|
42
|
+
/**
|
|
43
|
+
* Concrete implementation of the FeatureAccessPort.
|
|
44
|
+
*/
|
|
45
|
+
featureAccess?: Type<FeatureAccessPort>;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* When true the module is registered globally.
|
|
49
|
+
*/
|
|
50
|
+
isGlobal?: boolean;
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### 3. Dynamic Module with register()
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
// PaymentModule.ts
|
|
58
|
+
import { ConfigModule } from '@breadstone/archipel-platform-core';
|
|
59
|
+
import { type DynamicModule, Module, type Type } from '@nestjs/common';
|
|
60
|
+
|
|
61
|
+
@Module({})
|
|
62
|
+
export class PaymentModule {
|
|
63
|
+
|
|
64
|
+
public static register(options?: IPaymentModuleOptions): DynamicModule {
|
|
65
|
+
const providers = [StripeClient, FeatureGuard, FeatureUsageInterceptor];
|
|
66
|
+
|
|
67
|
+
if (options?.featureAccess) {
|
|
68
|
+
providers.push({
|
|
69
|
+
provide: FeatureAccessPort,
|
|
70
|
+
useClass: options.featureAccess,
|
|
71
|
+
} as never);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
global: options?.isGlobal ?? false,
|
|
76
|
+
module: PaymentModule,
|
|
77
|
+
imports: [ConfigModule.register('platform-payments', PLATFORM_PAYMENTS_CONFIG_ENTRIES)],
|
|
78
|
+
providers: providers,
|
|
79
|
+
exports: [StripeClient, FeatureGuard, FeatureUsageInterceptor],
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### 4. Consumer Registration
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
// In the consuming application:
|
|
90
|
+
@Module({
|
|
91
|
+
imports: [
|
|
92
|
+
PaymentModule.register({
|
|
93
|
+
featureAccess: MyFeatureAccessAdapter,
|
|
94
|
+
isGlobal: true,
|
|
95
|
+
}),
|
|
96
|
+
],
|
|
97
|
+
})
|
|
98
|
+
export class AppModule {}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Variants in the Codebase
|
|
102
|
+
|
|
103
|
+
### Simple: PaymentModule
|
|
104
|
+
|
|
105
|
+
- 1 optional port (`featureAccess`)
|
|
106
|
+
- Optional `isGlobal` flag
|
|
107
|
+
- Config via `ConfigModule.register()`
|
|
108
|
+
|
|
109
|
+
### Complex: AuthModule
|
|
110
|
+
|
|
111
|
+
- 4 required ports (`authSubject`, `mfaSubject`, `sessionPersistence`, `verificationSubject`)
|
|
112
|
+
- 2 optional ports (`socialAuth`, `tokenEnricher`)
|
|
113
|
+
- Conditional strategy registration (social OAuth only when `socialAuth` provided)
|
|
114
|
+
- Imports: PassportModule, JwtModule, MappingModule, EventModule, IdentifierModule
|
|
115
|
+
- Middleware configuration via `configure(consumer)`
|
|
116
|
+
|
|
117
|
+
### Provider-based: BlobModule
|
|
118
|
+
|
|
119
|
+
- Uses `forRoot()` instead of `register()` (global by default)
|
|
120
|
+
- Discriminated union for provider selection: `{ kind: 'Vercel' }` or `{ kind: 'Custom', useClass: ... }`
|
|
121
|
+
- Optional persistence ports (`objectPersistence`, `variantPersistence`)
|
|
122
|
+
|
|
123
|
+
### Infrastructure: DatabaseModule
|
|
124
|
+
|
|
125
|
+
- Both `forRoot()` (global) and `register()` (scoped) variants
|
|
126
|
+
- Factory provider for PrismaService with extensions
|
|
127
|
+
- Health indicator bootstrapping
|
|
128
|
+
|
|
129
|
+
### Async: McpModule
|
|
130
|
+
|
|
131
|
+
- `register(options)` for synchronous configuration
|
|
132
|
+
- `registerAsync({ useFactory, inject })` for async (e.g., reading config at runtime)
|
|
133
|
+
- `registerAsync({ useClass })` for factory class pattern
|
|
134
|
+
- `registerAsync({ useExisting })` for reusing an existing factory
|
|
135
|
+
|
|
136
|
+
## ConfigModule Integration
|
|
137
|
+
|
|
138
|
+
Every library module imports `ConfigModule.register()` to declare its configuration
|
|
139
|
+
dependencies:
|
|
140
|
+
|
|
141
|
+
```typescript
|
|
142
|
+
imports: [ConfigModule.register('platform-payments', PLATFORM_PAYMENTS_CONFIG_ENTRIES)]
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
This registers typed config keys into the global `ConfigRegistry` for validation
|
|
146
|
+
and discovery.
|
|
147
|
+
|
|
148
|
+
## MappingModule Integration
|
|
149
|
+
|
|
150
|
+
Modules that need object mapping import `MappingModule.withProfiles()`:
|
|
151
|
+
|
|
152
|
+
```typescript
|
|
153
|
+
imports: [MappingModule.withProfiles([SessionMappingProfile])]
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## Health Indicator Pattern
|
|
157
|
+
|
|
158
|
+
Infrastructure modules (Database, Blob) register health indicators:
|
|
159
|
+
|
|
160
|
+
```typescript
|
|
161
|
+
providers: [
|
|
162
|
+
BlobHealthIndicator,
|
|
163
|
+
{
|
|
164
|
+
provide: 'BLOB_HEALTH_INDICATOR_BOOTSTRAP',
|
|
165
|
+
useFactory: (orchestrator: HealthOrchestrator, indicator: BlobHealthIndicator) => {
|
|
166
|
+
orchestrator.registerIndicator(indicator);
|
|
167
|
+
return true;
|
|
168
|
+
},
|
|
169
|
+
inject: [HealthOrchestrator, BlobHealthIndicator],
|
|
170
|
+
},
|
|
171
|
+
]
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## Rules
|
|
175
|
+
|
|
176
|
+
1. **Ports are abstract classes** — not interfaces — so they work as DI tokens.
|
|
177
|
+
2. **`register()` for stateless config**, `registerAsync()` when config depends on runtime values.
|
|
178
|
+
3. **`forRoot()` for global singletons** (database, blob storage), `register()` for scoped modules.
|
|
179
|
+
4. **Always import ConfigModule.register()** to declare environment dependencies.
|
|
180
|
+
5. **Optional ports use conditional provider registration** (`if (options?.port)`).
|
|
181
|
+
6. **Required ports are listed in the options interface** without optional markers.
|
|
182
|
+
7. **Exports should list concrete services, guards, interceptors** — not internal implementation details.
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Query Pattern
|
|
3
|
+
description: Repository + Query pattern with IRepositoryQuery, factory functions, transactional queries, and rules.
|
|
4
|
+
order: 7
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Archipel Query Pattern (v1)
|
|
8
|
+
|
|
9
|
+
## Overview
|
|
10
|
+
|
|
11
|
+
In Archipel, data access follows the **Repository + Query** pattern. Repositories never expose
|
|
12
|
+
raw Prisma delegate methods to services. Instead, every data operation is wrapped in a
|
|
13
|
+
**query factory function** that returns an `IRepositoryQuery` object. Services call
|
|
14
|
+
`repository.execute(someQuery(...))` — never `repository.findFirst(...)` directly.
|
|
15
|
+
|
|
16
|
+
## Core Interface
|
|
17
|
+
|
|
18
|
+
```typescript
|
|
19
|
+
export interface IRepositoryQuery<TDelegate, TResult> {
|
|
20
|
+
readonly name: string;
|
|
21
|
+
run(model: TDelegate): Promise<TResult>;
|
|
22
|
+
}
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
- `TDelegate` — the Prisma delegate type (e.g. `Prisma.UserDelegate`)
|
|
26
|
+
- `TResult` — the query's return type
|
|
27
|
+
- `name` — identifier used for logging and debugging
|
|
28
|
+
|
|
29
|
+
## Factory Function
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
import { IRepositoryQuery } from '@breadstone/archipel-platform-database';
|
|
33
|
+
|
|
34
|
+
export function query<TDelegate, TResult>(
|
|
35
|
+
name: string,
|
|
36
|
+
run: (model: TDelegate) => Promise<TResult>,
|
|
37
|
+
): IRepositoryQuery<TDelegate, TResult> {
|
|
38
|
+
return { name, run };
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Type Extraction
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
export type QueryResultType<T> = T extends IRepositoryQuery<unknown, infer R> ? R : never;
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Use this to derive the result type from a query factory without duplicating types:
|
|
49
|
+
```typescript
|
|
50
|
+
type UserProfileResult = QueryResultType<ReturnType<typeof findUserProfileQuery>>;
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## How to Write a Query
|
|
54
|
+
|
|
55
|
+
### Step 1: Define the query factory function
|
|
56
|
+
|
|
57
|
+
Each query is a **named function** that returns an `IRepositoryQuery`. The function may accept
|
|
58
|
+
parameters — these become the query's runtime arguments.
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
import { Prisma } from '@prisma/client';
|
|
62
|
+
import { query } from '@breadstone/archipel-platform-database';
|
|
63
|
+
|
|
64
|
+
export function findUserByEmailQuery(email: string) {
|
|
65
|
+
return query<Prisma.UserDelegate, { id: string; email: string; name: string } | null>(
|
|
66
|
+
'findUserByEmail',
|
|
67
|
+
(model) =>
|
|
68
|
+
model.findFirst({
|
|
69
|
+
where: { email },
|
|
70
|
+
select: { id: true, email: true, name: true },
|
|
71
|
+
}),
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Step 2: Call from a service via repository.execute()
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
// In the service:
|
|
80
|
+
const user = await this._userRepository.execute(findUserByEmailQuery(email));
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Step 3: Extract result type if needed for mapping
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
type FindUserByEmailResult = QueryResultType<ReturnType<typeof findUserByEmailQuery>>;
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Transactional Queries
|
|
90
|
+
|
|
91
|
+
For operations that span multiple models, use `transactionalQuery`:
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
import { Prisma } from '@prisma/client';
|
|
95
|
+
import { transactionalQuery } from '@breadstone/archipel-platform-database';
|
|
96
|
+
|
|
97
|
+
export function createOrderWithItemsQuery(data: { userId: string; items: Array<{ productId: string; quantity: number }> }) {
|
|
98
|
+
return transactionalQuery<{ orderId: string }>(
|
|
99
|
+
'createOrderWithItems',
|
|
100
|
+
async (tx: Prisma.TransactionClient) => {
|
|
101
|
+
const order = await tx.order.create({
|
|
102
|
+
data: { userId: data.userId },
|
|
103
|
+
select: { id: true },
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
await tx.orderItem.createMany({
|
|
107
|
+
data: data.items.map((item) => ({
|
|
108
|
+
orderId: order.id,
|
|
109
|
+
productId: item.productId,
|
|
110
|
+
quantity: item.quantity,
|
|
111
|
+
})),
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
return { orderId: order.id };
|
|
115
|
+
},
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Called via:
|
|
121
|
+
```typescript
|
|
122
|
+
const result = await this._orderRepository.executeTransactional(
|
|
123
|
+
createOrderWithItemsQuery({ userId, items }),
|
|
124
|
+
);
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Rules
|
|
128
|
+
|
|
129
|
+
1. **Never call Prisma delegate methods directly from services.** Always go through `execute(query)`.
|
|
130
|
+
2. **Always select only the fields you need.** Use `select` — never return full records.
|
|
131
|
+
3. **Query names must be descriptive.** Use the pattern: `verbModelByConditionQuery`
|
|
132
|
+
(e.g. `findUserByEmailQuery`, `countOrdersByStatusQuery`, `updateUserNameQuery`).
|
|
133
|
+
4. **One query per file** for non-trivial queries. Small related queries may share a file.
|
|
134
|
+
5. **Type the result explicitly.** The second generic parameter of `query()` should be an inline
|
|
135
|
+
object type or a dedicated interface matching the `select` shape.
|
|
136
|
+
6. **Each query factory is a standalone function** (not a class method). Import `query` from
|
|
137
|
+
`@breadstone/archipel-platform-database`.
|