@breadstone/archipel-mcp 0.0.45 → 0.0.47
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/ai-text-generation.md +2 -2
- package/data/guides/authentication-and-authorization.md +2 -2
- package/data/guides/blob-storage.md +3 -3
- package/data/guides/caching.md +2 -2
- package/data/guides/composition-context.md +462 -0
- package/data/guides/database-setup.md +4 -4
- package/data/guides/email-delivery.md +6 -6
- package/data/guides/index.md +1 -0
- package/data/guides/openapi-and-feature-discovery.md +1 -1
- package/data/guides/queue-infrastructure.md +4 -4
- package/data/guides/telemetry-and-observability.md +1 -1
- package/data/packages/platform-blob-storage/api/Class.BlobStorageStrategyBase.md +1 -0
- package/data/packages/platform-blob-storage/api/Class.EmptyBlobStorageStrategy.md +183 -0
- package/data/packages/platform-blob-storage/api/Variable.AWS_S3_ACCESS_KEY_ID.md +1 -1
- package/data/packages/platform-blob-storage/api/Variable.AWS_S3_BUCKET.md +1 -1
- package/data/packages/platform-blob-storage/api/Variable.AWS_S3_CONFIG_ENTRIES.md +1 -1
- package/data/packages/platform-blob-storage/api/Variable.AWS_S3_ENDPOINT.md +1 -1
- package/data/packages/platform-blob-storage/api/Variable.AWS_S3_REGION.md +1 -1
- package/data/packages/platform-blob-storage/api/Variable.AWS_S3_SECRET_ACCESS_KEY.md +1 -1
- package/data/packages/platform-blob-storage/api/Variable.LOCAL_BLOB_BASE_PATH.md +1 -1
- package/data/packages/platform-blob-storage/api/Variable.LOCAL_BLOB_BUCKET.md +1 -1
- package/data/packages/platform-blob-storage/api/Variable.LOCAL_CONFIG_ENTRIES.md +1 -1
- package/data/packages/platform-blob-storage/api/Variable.PLATFORM_BLOB_STORAGE_CONFIG_ENTRIES.md +1 -1
- package/data/packages/platform-blob-storage/api/index.md +1 -0
- package/data/packages/platform-blob-storage/index.md +21 -1
- package/data/packages/platform-bootstrap/api/Class.CompositionContext.md +226 -0
- package/data/packages/platform-bootstrap/api/Class.CompositionModule.md +56 -0
- package/data/packages/platform-bootstrap/api/Class.CompositionResolver.md +206 -0
- package/data/packages/platform-bootstrap/api/Class.PlatformApplicationBuilder.md +75 -8
- package/data/packages/platform-bootstrap/api/Function.createCompositionContext.md +56 -0
- package/data/packages/platform-bootstrap/api/Interface.ICompositionContext.md +179 -0
- package/data/packages/platform-bootstrap/api/Interface.ICreateCompositionContextOptions.md +54 -0
- package/data/packages/platform-bootstrap/api/Interface.ICreateWithCompositionOptions.md +128 -0
- package/data/packages/platform-bootstrap/api/Interface.IProviderDecision.md +36 -0
- package/data/packages/platform-bootstrap/api/TypeAlias.CompositionImport.md +20 -0
- package/data/packages/platform-bootstrap/api/TypeAlias.ProviderComposer.md +53 -0
- package/data/packages/platform-bootstrap/api/index.md +10 -0
- package/data/packages/platform-cryptography/api/Class.OtpService.md +6 -6
- package/data/packages/platform-cryptography/api/Variable.TOTP_EPOCH_TOLERANCE.md +1 -1
- package/data/packages/platform-feature-flags/index.md +103 -0
- package/data/packages/platform-mailing/api/Class.DeliveryStrategyBase.md +0 -1
- package/data/packages/platform-mailing/api/index.md +0 -1
- package/package.json +1 -1
- package/data/packages/platform-mailing/api/Class.LogDeliveryStrategy.md +0 -71
|
@@ -64,7 +64,7 @@ Register only the provider loaders you install. The root entry does not import p
|
|
|
64
64
|
|
|
65
65
|
All intelligence settings are read from environment variables via `ConfigService`. No direct `process.env` access is needed.
|
|
66
66
|
|
|
67
|
-
```
|
|
67
|
+
```dotenv
|
|
68
68
|
INTELLIGENCE_PROVIDER=openai
|
|
69
69
|
INTELLIGENCE_MODEL=gpt-4o-mini
|
|
70
70
|
INTELLIGENCE_API_KEY=sk-...
|
|
@@ -85,7 +85,7 @@ INTELLIGENCE_BASE_URL= # optional — override for custom endpoints
|
|
|
85
85
|
|
|
86
86
|
Each provider resolves its API key and base URL from dedicated environment variables when the generic `INTELLIGENCE_API_KEY` is not set:
|
|
87
87
|
|
|
88
|
-
```
|
|
88
|
+
```dotenv
|
|
89
89
|
# Provider-specific keys (optional — fallback from INTELLIGENCE_API_KEY)
|
|
90
90
|
OPENAI_API_KEY=sk-...
|
|
91
91
|
ANTHROPIC_API_KEY=sk-ant-...
|
|
@@ -475,7 +475,7 @@ If `mfaRequired` is `true`, the client receives a `challengeId` instead of an `a
|
|
|
475
475
|
|
|
476
476
|
### MFA Configuration
|
|
477
477
|
|
|
478
|
-
```
|
|
478
|
+
```dotenv
|
|
479
479
|
AUTH_MFA_ENCRYPTION_KEY=<32-byte-hex-key>
|
|
480
480
|
AUTH_MFA_CHALLENGE_EXPIRES_IN=5m
|
|
481
481
|
AUTH_MFA_MAX_ATTEMPTS=5
|
|
@@ -584,7 +584,7 @@ AuthModule.register({
|
|
|
584
584
|
|
|
585
585
|
### Step 4: Set Environment Variables
|
|
586
586
|
|
|
587
|
-
```
|
|
587
|
+
```dotenv
|
|
588
588
|
# Google
|
|
589
589
|
AUTH_GOOGLE_CLIENT_ID=your-client-id
|
|
590
590
|
AUTH_GOOGLE_CLIENT_SECRET=your-client-secret
|
|
@@ -54,7 +54,7 @@ Each provider reads its credentials from environment variables via the `ConfigMo
|
|
|
54
54
|
|
|
55
55
|
### AWS S3
|
|
56
56
|
|
|
57
|
-
```
|
|
57
|
+
```dotenv
|
|
58
58
|
BLOB_AWS_REGION=eu-central-1
|
|
59
59
|
BLOB_AWS_BUCKET=my-app-uploads
|
|
60
60
|
BLOB_AWS_ACCESS_KEY_ID=AKIA...
|
|
@@ -67,7 +67,7 @@ BLOB_AWS_SECRET_ACCESS_KEY=secret...
|
|
|
67
67
|
|
|
68
68
|
You can authenticate with either a **connection string** or **DefaultAzureCredential** (via managed identity):
|
|
69
69
|
|
|
70
|
-
```
|
|
70
|
+
```dotenv
|
|
71
71
|
# Option 1: Connection string
|
|
72
72
|
BLOB_AZURE_CONNECTION_STRING=DefaultEndpointsProtocol=https;AccountName=...
|
|
73
73
|
BLOB_AZURE_CONTAINER=uploads
|
|
@@ -83,7 +83,7 @@ When using `accountName` without a `connectionString`, the provider authenticate
|
|
|
83
83
|
|
|
84
84
|
### Vercel Blob
|
|
85
85
|
|
|
86
|
-
```
|
|
86
|
+
```dotenv
|
|
87
87
|
BLOB_VERCEL_TOKEN=your-vercel-blob-token
|
|
88
88
|
```
|
|
89
89
|
|
package/data/guides/caching.md
CHANGED
|
@@ -28,7 +28,7 @@ yarn add ioredis
|
|
|
28
28
|
|
|
29
29
|
### Core Variables
|
|
30
30
|
|
|
31
|
-
```
|
|
31
|
+
```dotenv
|
|
32
32
|
CACHE_DEFAULT_TTL_MS=60000
|
|
33
33
|
CACHE_MAX_ENTRIES=1000
|
|
34
34
|
CACHE_STALE_WHILE_REVALIDATE=false
|
|
@@ -38,7 +38,7 @@ All core variables are optional. When omitted, each cache instance uses the valu
|
|
|
38
38
|
|
|
39
39
|
### Redis Variables
|
|
40
40
|
|
|
41
|
-
```
|
|
41
|
+
```dotenv
|
|
42
42
|
REDIS_URL=redis://localhost:6379
|
|
43
43
|
REDIS_KEY_PREFIX=app:
|
|
44
44
|
REDIS_TTL_SECONDS=300
|
|
@@ -0,0 +1,462 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Composition Context
|
|
3
|
+
description: Stage-aware, configuration-driven module composition for NestJS applications without direct process.env access.
|
|
4
|
+
order: 4
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Composition Context
|
|
8
|
+
|
|
9
|
+
The composition context pattern enables **stage-aware infrastructure module selection** at application startup. Rather than hard-coding provider imports or scattering `process.env` checks throughout your codebase, composition provides a clean abstraction that reads configuration once and determines which modules to import.
|
|
10
|
+
|
|
11
|
+
This is particularly valuable for:
|
|
12
|
+
|
|
13
|
+
- **Multi-stage deployments** — Use mock mail in development, real SMTP in production
|
|
14
|
+
- **Feature flags** — Toggle entire modules based on configuration
|
|
15
|
+
- **Provider switching** — Swap payment providers or blob storage backends per environment
|
|
16
|
+
- **Clean architecture** — Keep `main.ts` free of conditional logic
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Installation
|
|
21
|
+
|
|
22
|
+
The composition subpath is part of `platform-bootstrap`:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
yarn add @breadstone/archipel-platform-bootstrap @breadstone/archipel-platform-configuration
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Core Concepts
|
|
31
|
+
|
|
32
|
+
### Composition Context
|
|
33
|
+
|
|
34
|
+
The `ICompositionContext` provides typed access to configuration during module assembly:
|
|
35
|
+
|
|
36
|
+
```typescript
|
|
37
|
+
interface ICompositionContext {
|
|
38
|
+
readonly context: INestApplicationContext;
|
|
39
|
+
readonly config: ConfigService;
|
|
40
|
+
|
|
41
|
+
getConfigValue<T>(key: IConfigKey<T>): T;
|
|
42
|
+
tryGetConfigValue<T>(key: IConfigKey<T>, fallback: T): T;
|
|
43
|
+
getService<T>(token: Type<T>): T;
|
|
44
|
+
close(): Promise<void>;
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Composition Resolver
|
|
49
|
+
|
|
50
|
+
Abstract base class for encapsulating composition decisions:
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
abstract class CompositionResolver {
|
|
54
|
+
abstract shouldImportProvider(providerKey: string): boolean;
|
|
55
|
+
|
|
56
|
+
protected getConfigValue<T>(key: IConfigKey<T>): T;
|
|
57
|
+
protected tryGetConfigValue<T>(key: IConfigKey<T>, fallback: T): T;
|
|
58
|
+
protected compareConfigValue<T>(key: IConfigKey<T>, expected: T): boolean;
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Composition Import
|
|
63
|
+
|
|
64
|
+
Type alias for valid NestJS module imports:
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
type CompositionImport = Type<unknown> | DynamicModule | Promise<DynamicModule> | ForwardReference<unknown>;
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## Basic Usage
|
|
73
|
+
|
|
74
|
+
### Direct Factory Approach
|
|
75
|
+
|
|
76
|
+
For simple cases, use `createCompositionContext()` directly:
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
import { NestFactory } from '@nestjs/core';
|
|
80
|
+
import { createCompositionContext } from '@breadstone/archipel-platform-bootstrap/composition';
|
|
81
|
+
import { AppModule } from './AppModule';
|
|
82
|
+
|
|
83
|
+
async function main(): Promise<void> {
|
|
84
|
+
const context = await createCompositionContext();
|
|
85
|
+
|
|
86
|
+
try {
|
|
87
|
+
const driver = context.tryGetConfigValue({ key: 'MAIL_DRIVER' }, 'log');
|
|
88
|
+
|
|
89
|
+
if (driver === 'smtp') {
|
|
90
|
+
// Dynamic import only when needed
|
|
91
|
+
const { MailModule } = await import('@breadstone/archipel-platform-mailing');
|
|
92
|
+
const { SmtpDeliveryStrategy } = await import('@breadstone/archipel-platform-mailing/delivering/smtp');
|
|
93
|
+
|
|
94
|
+
console.log('Using SMTP mail delivery');
|
|
95
|
+
}
|
|
96
|
+
} finally {
|
|
97
|
+
await context.close();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Continue with normal NestJS bootstrap
|
|
101
|
+
const app = await NestFactory.create(AppModule);
|
|
102
|
+
await app.listen(3000);
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Builder Integration
|
|
107
|
+
|
|
108
|
+
For full integration with `PlatformApplicationBuilder`:
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
import { PlatformApplicationBuilder, useApi, CompositionImport } from '@breadstone/archipel-platform-bootstrap';
|
|
112
|
+
import { ExpressAdapter, NestExpressApplication } from '@nestjs/platform-express';
|
|
113
|
+
import { NestFactory } from '@nestjs/core';
|
|
114
|
+
import { AppModule } from './AppModule';
|
|
115
|
+
|
|
116
|
+
async function main(): Promise<void> {
|
|
117
|
+
const builder = await PlatformApplicationBuilder.createWithComposition<NestExpressApplication>(
|
|
118
|
+
async (context) => {
|
|
119
|
+
const imports: CompositionImport[] = [AppModule];
|
|
120
|
+
|
|
121
|
+
// Stage-aware mail provider selection
|
|
122
|
+
const driver = context.tryGetConfigValue({ key: 'MAIL_DRIVER' }, 'log');
|
|
123
|
+
const stage = context.tryGetConfigValue({ key: 'NODE_ENV' }, 'development');
|
|
124
|
+
|
|
125
|
+
if (driver === 'smtp' || stage === 'production') {
|
|
126
|
+
const { MailModule } = await import('@breadstone/archipel-platform-mailing');
|
|
127
|
+
const { SmtpDeliveryStrategy } = await import('@breadstone/archipel-platform-mailing/delivering/smtp');
|
|
128
|
+
imports.push(MailModule.register({ deliveryStrategy: SmtpDeliveryStrategy }));
|
|
129
|
+
} else {
|
|
130
|
+
const { MailModule } = await import('@breadstone/archipel-platform-mailing');
|
|
131
|
+
const { LogDeliveryStrategy } = await import('@breadstone/archipel-platform-mailing/delivering/log');
|
|
132
|
+
imports.push(MailModule.register({ deliveryStrategy: LogDeliveryStrategy }));
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return imports;
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
appFactory: (module) => NestFactory.create<NestExpressApplication>(module, new ExpressAdapter()),
|
|
139
|
+
}
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
await builder
|
|
143
|
+
.use(useApi({ prefix: 'api' }))
|
|
144
|
+
.listenFromConfig({ fallbackPort: 3000 });
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
void main();
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## Custom Composition Resolver
|
|
153
|
+
|
|
154
|
+
For complex applications, encapsulate composition logic in a dedicated resolver class:
|
|
155
|
+
|
|
156
|
+
### 1. Define the Resolver
|
|
157
|
+
|
|
158
|
+
```typescript
|
|
159
|
+
// src/composition/AppCompositionResolver.ts
|
|
160
|
+
import { Injectable } from '@nestjs/common';
|
|
161
|
+
import { ConfigService, createConfigKey } from '@breadstone/archipel-platform-configuration';
|
|
162
|
+
import { CompositionResolver } from '@breadstone/archipel-platform-bootstrap/composition';
|
|
163
|
+
|
|
164
|
+
const MAIL_DRIVER = createConfigKey<string>('MAIL_DRIVER');
|
|
165
|
+
const NODE_ENV = createConfigKey<string>('NODE_ENV');
|
|
166
|
+
const FEATURE_NEW_PAYMENTS = createConfigKey<string>('FEATURE_NEW_PAYMENTS');
|
|
167
|
+
|
|
168
|
+
@Injectable()
|
|
169
|
+
export class AppCompositionResolver extends CompositionResolver {
|
|
170
|
+
public constructor(configService: ConfigService) {
|
|
171
|
+
super(configService);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
public shouldImportProvider(providerKey: string): boolean {
|
|
175
|
+
switch (providerKey) {
|
|
176
|
+
case 'smtp-mail':
|
|
177
|
+
return this.shouldUseSmtpMail();
|
|
178
|
+
case 'log-mail':
|
|
179
|
+
return !this.shouldUseSmtpMail();
|
|
180
|
+
case 'stripe-payments':
|
|
181
|
+
return this.shouldUseStripePayments();
|
|
182
|
+
case 'paddle-payments':
|
|
183
|
+
return !this.shouldUseStripePayments();
|
|
184
|
+
default:
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
public shouldUseSmtpMail(): boolean {
|
|
190
|
+
const driver = this.tryGetConfigValue(MAIL_DRIVER, 'log');
|
|
191
|
+
return driver === 'smtp' || this.isProductionLike();
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
public shouldUseStripePayments(): boolean {
|
|
195
|
+
const feature = this.tryGetConfigValue(FEATURE_NEW_PAYMENTS, 'disabled');
|
|
196
|
+
return feature !== 'paddle';
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
public isProductionLike(): boolean {
|
|
200
|
+
const stage = this.tryGetConfigValue(NODE_ENV, 'development');
|
|
201
|
+
return stage === 'production' || stage === 'staging';
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### 2. Create a Composition Module
|
|
207
|
+
|
|
208
|
+
```typescript
|
|
209
|
+
// src/composition/AppCompositionModule.ts
|
|
210
|
+
import { Module } from '@nestjs/common';
|
|
211
|
+
import { ConfigModule, EnvironmentConfigStrategy } from '@breadstone/archipel-platform-configuration';
|
|
212
|
+
import { AppCompositionResolver } from './AppCompositionResolver';
|
|
213
|
+
|
|
214
|
+
@Module({
|
|
215
|
+
imports: [
|
|
216
|
+
ConfigModule.forRoot({
|
|
217
|
+
strategyFactory: () => new EnvironmentConfigStrategy(),
|
|
218
|
+
}),
|
|
219
|
+
],
|
|
220
|
+
providers: [AppCompositionResolver],
|
|
221
|
+
exports: [ConfigModule, AppCompositionResolver],
|
|
222
|
+
})
|
|
223
|
+
export class AppCompositionModule {}
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
### 3. Use in Bootstrap
|
|
227
|
+
|
|
228
|
+
```typescript
|
|
229
|
+
// src/main.ts
|
|
230
|
+
import { PlatformApplicationBuilder, CompositionImport } from '@breadstone/archipel-platform-bootstrap';
|
|
231
|
+
import { AppCompositionModule, AppCompositionResolver } from './composition';
|
|
232
|
+
import { AppModule } from './AppModule';
|
|
233
|
+
|
|
234
|
+
async function main(): Promise<void> {
|
|
235
|
+
const builder = await PlatformApplicationBuilder.createWithComposition(
|
|
236
|
+
async (context) => {
|
|
237
|
+
const resolver = context.getService(AppCompositionResolver);
|
|
238
|
+
const imports: CompositionImport[] = [AppModule];
|
|
239
|
+
|
|
240
|
+
// Mail provider
|
|
241
|
+
if (resolver.shouldImportProvider('smtp-mail')) {
|
|
242
|
+
const { MailModule } = await import('@breadstone/archipel-platform-mailing');
|
|
243
|
+
const { SmtpDeliveryStrategy } = await import('@breadstone/archipel-platform-mailing/delivering/smtp');
|
|
244
|
+
imports.push(MailModule.register({ deliveryStrategy: SmtpDeliveryStrategy }));
|
|
245
|
+
} else {
|
|
246
|
+
const { MailModule } = await import('@breadstone/archipel-platform-mailing');
|
|
247
|
+
const { LogDeliveryStrategy } = await import('@breadstone/archipel-platform-mailing/delivering/log');
|
|
248
|
+
imports.push(MailModule.register({ deliveryStrategy: LogDeliveryStrategy }));
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Payment provider
|
|
252
|
+
if (resolver.shouldImportProvider('stripe-payments')) {
|
|
253
|
+
const { PaymentModule } = await import('@breadstone/archipel-platform-payments');
|
|
254
|
+
const { StripeClient } = await import('@breadstone/archipel-platform-payments/stripe');
|
|
255
|
+
imports.push(PaymentModule.register({ paymentClient: StripeClient }));
|
|
256
|
+
} else if (resolver.shouldImportProvider('paddle-payments')) {
|
|
257
|
+
const { PaymentModule } = await import('@breadstone/archipel-platform-payments');
|
|
258
|
+
const { PaddleClient } = await import('@breadstone/archipel-platform-payments/paddle');
|
|
259
|
+
imports.push(PaymentModule.register({ paymentClient: PaddleClient }));
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return imports;
|
|
263
|
+
},
|
|
264
|
+
{
|
|
265
|
+
compositionModule: AppCompositionModule,
|
|
266
|
+
}
|
|
267
|
+
);
|
|
268
|
+
|
|
269
|
+
await builder.listenFromConfig({ fallbackPort: 3000 });
|
|
270
|
+
}
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
---
|
|
274
|
+
|
|
275
|
+
## Provider Composer Pattern
|
|
276
|
+
|
|
277
|
+
For reusable provider composition, define composer functions:
|
|
278
|
+
|
|
279
|
+
```typescript
|
|
280
|
+
// src/composition/composers/composeMailProvider.ts
|
|
281
|
+
import type { ICompositionContext } from '@breadstone/archipel-platform-bootstrap/composition';
|
|
282
|
+
import type { CompositionImport } from '@breadstone/archipel-platform-bootstrap';
|
|
283
|
+
import { AppCompositionResolver } from '../AppCompositionResolver';
|
|
284
|
+
|
|
285
|
+
export async function composeMailProvider(context: ICompositionContext): Promise<CompositionImport | null> {
|
|
286
|
+
const resolver = context.getService(AppCompositionResolver);
|
|
287
|
+
|
|
288
|
+
if (resolver.shouldImportProvider('smtp-mail')) {
|
|
289
|
+
const { MailModule } = await import('@breadstone/archipel-platform-mailing');
|
|
290
|
+
const { SmtpDeliveryStrategy } = await import('@breadstone/archipel-platform-mailing/delivering/smtp');
|
|
291
|
+
return MailModule.register({ deliveryStrategy: SmtpDeliveryStrategy });
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
if (resolver.shouldImportProvider('log-mail')) {
|
|
295
|
+
const { MailModule } = await import('@breadstone/archipel-platform-mailing');
|
|
296
|
+
const { LogDeliveryStrategy } = await import('@breadstone/archipel-platform-mailing/delivering/log');
|
|
297
|
+
return MailModule.register({ deliveryStrategy: LogDeliveryStrategy });
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
return null;
|
|
301
|
+
}
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
Then compose cleanly in main:
|
|
305
|
+
|
|
306
|
+
```typescript
|
|
307
|
+
const builder = await PlatformApplicationBuilder.createWithComposition(
|
|
308
|
+
async (context) => {
|
|
309
|
+
const imports: CompositionImport[] = [AppModule];
|
|
310
|
+
|
|
311
|
+
const mailModule = await composeMailProvider(context);
|
|
312
|
+
if (mailModule) imports.push(mailModule);
|
|
313
|
+
|
|
314
|
+
const paymentModule = await composePaymentProvider(context);
|
|
315
|
+
if (paymentModule) imports.push(paymentModule);
|
|
316
|
+
|
|
317
|
+
return imports;
|
|
318
|
+
},
|
|
319
|
+
{ compositionModule: AppCompositionModule }
|
|
320
|
+
);
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
---
|
|
324
|
+
|
|
325
|
+
## Testing Composition
|
|
326
|
+
|
|
327
|
+
### Unit Testing a Resolver
|
|
328
|
+
|
|
329
|
+
```typescript
|
|
330
|
+
import { describe, it, expect, vi, afterEach } from 'vitest';
|
|
331
|
+
import { ConfigService } from '@breadstone/archipel-platform-configuration';
|
|
332
|
+
import { AppCompositionResolver } from './AppCompositionResolver';
|
|
333
|
+
|
|
334
|
+
describe('AppCompositionResolver', () => {
|
|
335
|
+
afterEach(() => {
|
|
336
|
+
vi.restoreAllMocks();
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
it('uses SMTP in production regardless of driver setting', () => {
|
|
340
|
+
const configService = {
|
|
341
|
+
get: vi.fn((key: string) => {
|
|
342
|
+
if (key === 'MAIL_DRIVER') return 'log';
|
|
343
|
+
if (key === 'NODE_ENV') return 'production';
|
|
344
|
+
return undefined;
|
|
345
|
+
}),
|
|
346
|
+
tryGet: vi.fn((key: string, fallback: string) => {
|
|
347
|
+
if (key === 'MAIL_DRIVER') return 'log';
|
|
348
|
+
if (key === 'NODE_ENV') return 'production';
|
|
349
|
+
return fallback;
|
|
350
|
+
}),
|
|
351
|
+
} as unknown as ConfigService;
|
|
352
|
+
|
|
353
|
+
const resolver = new AppCompositionResolver(configService);
|
|
354
|
+
|
|
355
|
+
expect(resolver.shouldImportProvider('smtp-mail')).toBe(true);
|
|
356
|
+
expect(resolver.shouldImportProvider('log-mail')).toBe(false);
|
|
357
|
+
});
|
|
358
|
+
});
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
### Integration Testing with Real Context
|
|
362
|
+
|
|
363
|
+
```typescript
|
|
364
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
365
|
+
import { createCompositionContext } from '@breadstone/archipel-platform-bootstrap/composition';
|
|
366
|
+
import { AppCompositionModule, AppCompositionResolver } from './composition';
|
|
367
|
+
|
|
368
|
+
describe('Composition Integration', () => {
|
|
369
|
+
let originalEnv: Record<string, string | undefined>;
|
|
370
|
+
|
|
371
|
+
beforeEach(() => {
|
|
372
|
+
originalEnv = {
|
|
373
|
+
MAIL_DRIVER: process.env['MAIL_DRIVER'],
|
|
374
|
+
NODE_ENV: process.env['NODE_ENV'],
|
|
375
|
+
};
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
afterEach(() => {
|
|
379
|
+
for (const [key, value] of Object.entries(originalEnv)) {
|
|
380
|
+
if (value === undefined) {
|
|
381
|
+
delete process.env[key];
|
|
382
|
+
} else {
|
|
383
|
+
process.env[key] = value;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
it('resolves correct mail provider for production', async () => {
|
|
389
|
+
process.env['NODE_ENV'] = 'production';
|
|
390
|
+
process.env['MAIL_DRIVER'] = 'log';
|
|
391
|
+
|
|
392
|
+
const context = await createCompositionContext({
|
|
393
|
+
module: AppCompositionModule,
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
try {
|
|
397
|
+
const resolver = context.getService(AppCompositionResolver);
|
|
398
|
+
expect(resolver.shouldImportProvider('smtp-mail')).toBe(true);
|
|
399
|
+
} finally {
|
|
400
|
+
await context.close();
|
|
401
|
+
}
|
|
402
|
+
});
|
|
403
|
+
});
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
---
|
|
407
|
+
|
|
408
|
+
## API Reference
|
|
409
|
+
|
|
410
|
+
### createCompositionContext
|
|
411
|
+
|
|
412
|
+
```typescript
|
|
413
|
+
function createCompositionContext(
|
|
414
|
+
options?: ICreateCompositionContextOptions
|
|
415
|
+
): Promise<ICompositionContext>;
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
| Option | Type | Default | Description |
|
|
419
|
+
| ------ | ---- | ------- | ----------- |
|
|
420
|
+
| `module` | `Type<unknown>` | `CompositionModule` | Custom composition module |
|
|
421
|
+
| `enableLogging` | `boolean` | `false` | Enable NestJS logger output |
|
|
422
|
+
|
|
423
|
+
### PlatformApplicationBuilder.createWithComposition
|
|
424
|
+
|
|
425
|
+
```typescript
|
|
426
|
+
static async createWithComposition<TApp extends INestApplication>(
|
|
427
|
+
composer: (context: ICompositionContext) => Promise<CompositionImport[]> | CompositionImport[],
|
|
428
|
+
options?: ICreateWithCompositionOptions<TApp>
|
|
429
|
+
): Promise<PlatformApplicationBuilder<TApp>>;
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
| Option | Type | Default | Description |
|
|
433
|
+
| ------ | ---- | ------- | ----------- |
|
|
434
|
+
| `compositionModule` | `Type<unknown>` | `CompositionModule` | Custom composition module |
|
|
435
|
+
| `enableCompositionLogging` | `boolean` | `false` | Enable logging during composition |
|
|
436
|
+
| `nestOptions` | `NestApplicationOptions` | `undefined` | Options for `NestFactory.create()` |
|
|
437
|
+
| `platformOptions` | `IPlatformApplicationOptions` | `undefined` | Options for the builder |
|
|
438
|
+
| `appFactory` | `(module: DynamicModule) => Promise<TApp>` | `undefined` | Custom app factory |
|
|
439
|
+
|
|
440
|
+
---
|
|
441
|
+
|
|
442
|
+
## Best Practices
|
|
443
|
+
|
|
444
|
+
1. **Keep composition modules minimal** — Only configuration and resolver services. No database, queues, or heavy infrastructure.
|
|
445
|
+
|
|
446
|
+
2. **Use dynamic imports** — Import provider modules only when needed to reduce bundle size and startup time.
|
|
447
|
+
|
|
448
|
+
3. **Encapsulate decisions in resolvers** — Don't scatter `process.env` checks. Centralize in a `CompositionResolver`.
|
|
449
|
+
|
|
450
|
+
4. **Test resolvers in isolation** — Unit test the decision logic separately from NestJS bootstrap.
|
|
451
|
+
|
|
452
|
+
5. **Close the context** — Always call `context.close()` when using `createCompositionContext()` directly.
|
|
453
|
+
|
|
454
|
+
6. **Prefer `tryGetConfigValue` over `getConfigValue`** — Provide sensible defaults for optional configuration.
|
|
455
|
+
|
|
456
|
+
---
|
|
457
|
+
|
|
458
|
+
## See Also
|
|
459
|
+
|
|
460
|
+
- [Application Bootstrap](./application-bootstrap) — Standard bootstrap pipeline without composition
|
|
461
|
+
- [Configuration Management](./configuration) — Typed configuration keys and strategies
|
|
462
|
+
- [Implementing Ports](./implementing-ports) — Port/adapter pattern for swappable implementations
|
|
@@ -48,19 +48,19 @@ npx prisma init
|
|
|
48
48
|
|
|
49
49
|
`platform-database` ships its own config keys and a ready-made entries array. Set the relevant variables in your environment:
|
|
50
50
|
|
|
51
|
-
```
|
|
51
|
+
```dotenv
|
|
52
52
|
DATABASE_URL=postgresql://user:password@localhost:5432/mydb
|
|
53
53
|
```
|
|
54
54
|
|
|
55
55
|
For Prisma Accelerate (managed connection pooling), add the Accelerate URL from the Prisma Console:
|
|
56
56
|
|
|
57
|
-
```
|
|
57
|
+
```dotenv
|
|
58
58
|
DB_ACCELERATE_URL=prisma://accelerate.prisma-data.net/?api_key=your-accelerate-api-key
|
|
59
59
|
```
|
|
60
60
|
|
|
61
61
|
Optional environment variables for Prisma Optimize (query analysis dashboard):
|
|
62
62
|
|
|
63
|
-
```
|
|
63
|
+
```dotenv
|
|
64
64
|
DB_OPTIMIZE=true
|
|
65
65
|
DB_OPTIMIZE_API_KEY=your-optimize-api-key
|
|
66
66
|
```
|
|
@@ -120,7 +120,7 @@ Accelerate provides connection pooling, global caching, and query optimization
|
|
|
120
120
|
|
|
121
121
|
Set `DB_ACCELERATE_URL` in your environment. The module reads it automatically via `ConfigService` — no code changes needed:
|
|
122
122
|
|
|
123
|
-
```
|
|
123
|
+
```dotenv
|
|
124
124
|
DB_ACCELERATE_URL=prisma://accelerate.prisma-data.net/?api_key=your-accelerate-api-key
|
|
125
125
|
```
|
|
126
126
|
|
|
@@ -96,7 +96,7 @@ This ensures that importing the core package does **not** pull in provider-speci
|
|
|
96
96
|
|
|
97
97
|
### Core Variables
|
|
98
98
|
|
|
99
|
-
```
|
|
99
|
+
```dotenv
|
|
100
100
|
MAIL_TEMPLATE_ENGINE_FORMAT=html
|
|
101
101
|
MAIL_SENDER_EMAIL=noreply@yourapp.com
|
|
102
102
|
```
|
|
@@ -109,7 +109,7 @@ The delivery strategy and template-fetch strategy are no longer configured via e
|
|
|
109
109
|
|
|
110
110
|
#### SMTP
|
|
111
111
|
|
|
112
|
-
```
|
|
112
|
+
```dotenv
|
|
113
113
|
MAIL_SMTP_HOST=smtp.example.com
|
|
114
114
|
MAIL_SMTP_PORT=587
|
|
115
115
|
MAIL_SMTP_USER=user@example.com
|
|
@@ -119,25 +119,25 @@ MAIL_SMTP_SECURE=true
|
|
|
119
119
|
|
|
120
120
|
#### Resend
|
|
121
121
|
|
|
122
|
-
```
|
|
122
|
+
```dotenv
|
|
123
123
|
MAIL_RESEND_API_KEY=re_...
|
|
124
124
|
```
|
|
125
125
|
|
|
126
126
|
#### Postmark
|
|
127
127
|
|
|
128
|
-
```
|
|
128
|
+
```dotenv
|
|
129
129
|
MAIL_POSTMARK_API_KEY=...
|
|
130
130
|
```
|
|
131
131
|
|
|
132
132
|
#### SendGrid
|
|
133
133
|
|
|
134
|
-
```
|
|
134
|
+
```dotenv
|
|
135
135
|
MAIL_SENDGRID_API_KEY=SG...
|
|
136
136
|
```
|
|
137
137
|
|
|
138
138
|
#### Mailgun
|
|
139
139
|
|
|
140
|
-
```
|
|
140
|
+
```dotenv
|
|
141
141
|
MAIL_MAILGUN_API_KEY=key-...
|
|
142
142
|
MAIL_MAILGUN_DOMAIN=mg.yourapp.com
|
|
143
143
|
```
|
package/data/guides/index.md
CHANGED
|
@@ -14,6 +14,7 @@ Practical guides for working with Archipel packages in your NestJS application.
|
|
|
14
14
|
| [Getting Started](./getting-started) | Install packages, register your first module, implement ports, and run the app. |
|
|
15
15
|
| [Configuration Management](./configuration) | Type-safe config keys, strategy-based resolution, central registry, and validation. |
|
|
16
16
|
| [Application Bootstrap](./application-bootstrap) | Compose NestJS startup with platform-bootstrap, Express middleware, security, sessions, and OpenAPI. |
|
|
17
|
+
| [Composition Context](./composition-context) | Stage-aware, configuration-driven module composition without direct process.env access. |
|
|
17
18
|
| [Implementing Ports](./implementing-ports) | How the port/adapter pattern works, how to write adapters, and how to test them. |
|
|
18
19
|
| [Controller Best Practices](./controller-best-practices) | Structure, annotate, and implement controllers — regions, DI, ResponseReturn, guards, and the @Api decorator. |
|
|
19
20
|
| [Request & Response DTOs](./request-response-dtos) | DTO naming conventions, validation decorators, cross-field validation, and class-transformer integration. |
|
|
@@ -40,7 +40,7 @@ yarn add @vercel/queue
|
|
|
40
40
|
|
|
41
41
|
### Core Variables
|
|
42
42
|
|
|
43
|
-
```
|
|
43
|
+
```dotenv
|
|
44
44
|
QUEUE_MAX_JOBS=10000
|
|
45
45
|
```
|
|
46
46
|
|
|
@@ -48,7 +48,7 @@ QUEUE_MAX_JOBS=10000
|
|
|
48
48
|
|
|
49
49
|
### BullMQ Variables
|
|
50
50
|
|
|
51
|
-
```
|
|
51
|
+
```dotenv
|
|
52
52
|
BULLMQ_REDIS_URL=redis://localhost:6379
|
|
53
53
|
BULLMQ_PREFIX=myapp:
|
|
54
54
|
```
|
|
@@ -57,7 +57,7 @@ BULLMQ_PREFIX=myapp:
|
|
|
57
57
|
|
|
58
58
|
### Azure Service Bus Variables
|
|
59
59
|
|
|
60
|
-
```
|
|
60
|
+
```dotenv
|
|
61
61
|
AZURE_CONNECTION_STRING=Endpoint=sb://my-namespace.servicebus.windows.net/;SharedAccessKeyName=...;SharedAccessKey=...
|
|
62
62
|
AZURE_RECEIVE_WAIT_MS=5000
|
|
63
63
|
```
|
|
@@ -66,7 +66,7 @@ AZURE_RECEIVE_WAIT_MS=5000
|
|
|
66
66
|
|
|
67
67
|
### Vercel Queues Variables
|
|
68
68
|
|
|
69
|
-
```
|
|
69
|
+
```dotenv
|
|
70
70
|
VERCEL_QUEUE_REGION=iad1
|
|
71
71
|
VERCEL_QUEUE_CONSUMER_GROUP=my-consumer
|
|
72
72
|
VERCEL_QUEUE_TOKEN=
|