@breadstone/archipel-mcp 0.0.46 → 0.0.48
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/composition-context.md +462 -0
- package/data/guides/index.md +1 -0
- 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-database/api/Class.DatabaseModule.md +22 -7
- package/data/packages/platform-database/api/Interface.IRepositoryQuery2.md +2 -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
|
@@ -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
|
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. |
|
|
@@ -16,6 +16,7 @@ Serves as both the type contract and the NestJS DI token type reference.
|
|
|
16
16
|
- [`VercelBlobStorageStrategy`](Class.VercelBlobStorageStrategy)
|
|
17
17
|
- [`AzureBlobStorageStrategy`](Class.AzureBlobStorageStrategy)
|
|
18
18
|
- [`AwsS3BlobStorageStrategy`](Class.AwsS3BlobStorageStrategy)
|
|
19
|
+
- [`EmptyBlobStorageStrategy`](Class.EmptyBlobStorageStrategy)
|
|
19
20
|
- [`LocalBlobStorageStrategy`](Class.LocalBlobStorageStrategy)
|
|
20
21
|
|
|
21
22
|
## Constructors
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: 'Class: EmptyBlobStorageStrategy'
|
|
3
|
+
generated: true
|
|
4
|
+
editUrl: false
|
|
5
|
+
---
|
|
6
|
+
# Class: EmptyBlobStorageStrategy
|
|
7
|
+
|
|
8
|
+
Defined in: [strategies/empty/EmptyBlobStorageStrategy.ts:15](https://github.com/RueDeRennes/archipel/blob/main/libs/platform-blob-storage/src/strategies/empty/EmptyBlobStorageStrategy.ts#L15)
|
|
9
|
+
|
|
10
|
+
Provides a blob storage strategy backed by the local filesystem.
|
|
11
|
+
|
|
12
|
+
## Extends
|
|
13
|
+
|
|
14
|
+
- [`BlobStorageStrategyBase`](Class.BlobStorageStrategyBase)
|
|
15
|
+
|
|
16
|
+
## Constructors
|
|
17
|
+
|
|
18
|
+
### Constructor
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
new EmptyBlobStorageStrategy(): EmptyBlobStorageStrategy;
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Defined in: [strategies/empty/EmptyBlobStorageStrategy.ts:29](https://github.com/RueDeRennes/archipel/blob/main/libs/platform-blob-storage/src/strategies/empty/EmptyBlobStorageStrategy.ts#L29)
|
|
25
|
+
|
|
26
|
+
Constructs a new instance of the `EmptyBlobStorageStrategy` class.
|
|
27
|
+
|
|
28
|
+
#### Returns
|
|
29
|
+
|
|
30
|
+
`EmptyBlobStorageStrategy`
|
|
31
|
+
|
|
32
|
+
#### Overrides
|
|
33
|
+
|
|
34
|
+
[`BlobStorageStrategyBase`](Class.BlobStorageStrategyBase).[`constructor`](Class.BlobStorageStrategyBase#constructor)
|
|
35
|
+
|
|
36
|
+
## Properties
|
|
37
|
+
|
|
38
|
+
### defaultBucket?
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
optional defaultBucket?: string = undefined;
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Defined in: [strategies/empty/EmptyBlobStorageStrategy.ts:39](https://github.com/RueDeRennes/archipel/blob/main/libs/platform-blob-storage/src/strategies/empty/EmptyBlobStorageStrategy.ts#L39)
|
|
45
|
+
|
|
46
|
+
Gets the default bucket (container/directory) configured for the provider.
|
|
47
|
+
|
|
48
|
+
#### Overrides
|
|
49
|
+
|
|
50
|
+
[`BlobStorageStrategyBase`](Class.BlobStorageStrategyBase).[`defaultBucket`](Class.BlobStorageStrategyBase#defaultbucket)
|
|
51
|
+
|
|
52
|
+
***
|
|
53
|
+
|
|
54
|
+
### providerId
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
readonly providerId: string = 'empty';
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Defined in: [strategies/empty/EmptyBlobStorageStrategy.ts:37](https://github.com/RueDeRennes/archipel/blob/main/libs/platform-blob-storage/src/strategies/empty/EmptyBlobStorageStrategy.ts#L37)
|
|
61
|
+
|
|
62
|
+
Gets the unique identifier for this storage provider.
|
|
63
|
+
|
|
64
|
+
#### Overrides
|
|
65
|
+
|
|
66
|
+
[`BlobStorageStrategyBase`](Class.BlobStorageStrategyBase).[`providerId`](Class.BlobStorageStrategyBase#providerid)
|
|
67
|
+
|
|
68
|
+
## Methods
|
|
69
|
+
|
|
70
|
+
### createSignedUrl()?
|
|
71
|
+
|
|
72
|
+
```ts
|
|
73
|
+
optional createSignedUrl(request): Promise<string>;
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Defined in: [strategies/abstracts/BlobStorageStrategyBase.ts:79](https://github.com/RueDeRennes/archipel/blob/main/libs/platform-blob-storage/src/strategies/abstracts/BlobStorageStrategyBase.ts#L79)
|
|
77
|
+
|
|
78
|
+
Creates a signed URL for the specified blob object.
|
|
79
|
+
Not all providers support this operation.
|
|
80
|
+
|
|
81
|
+
#### Parameters
|
|
82
|
+
|
|
83
|
+
| Parameter | Type | Description |
|
|
84
|
+
| ------ | ------ | ------ |
|
|
85
|
+
| `request` | [`IBlobSignedUrlRequest`](Interface.IBlobSignedUrlRequest) | The request describing the object to link. |
|
|
86
|
+
|
|
87
|
+
#### Returns
|
|
88
|
+
|
|
89
|
+
`Promise`\<`string`\>
|
|
90
|
+
|
|
91
|
+
The generated signed URL.
|
|
92
|
+
|
|
93
|
+
#### Inherited from
|
|
94
|
+
|
|
95
|
+
[`BlobStorageStrategyBase`](Class.BlobStorageStrategyBase).[`createSignedUrl`](Class.BlobStorageStrategyBase#createsignedurl)
|
|
96
|
+
|
|
97
|
+
***
|
|
98
|
+
|
|
99
|
+
### deleteObject()
|
|
100
|
+
|
|
101
|
+
```ts
|
|
102
|
+
deleteObject(request): Promise<void>;
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Defined in: [strategies/empty/EmptyBlobStorageStrategy.ts:93](https://github.com/RueDeRennes/archipel/blob/main/libs/platform-blob-storage/src/strategies/empty/EmptyBlobStorageStrategy.ts#L93)
|
|
106
|
+
|
|
107
|
+
Deletes a blob object from the storage backend.
|
|
108
|
+
|
|
109
|
+
#### Parameters
|
|
110
|
+
|
|
111
|
+
| Parameter | Type | Description |
|
|
112
|
+
| ------ | ------ | ------ |
|
|
113
|
+
| `request` | [`IBlobDeleteRequest`](Interface.IBlobDeleteRequest) | The delete request describing the object to remove. |
|
|
114
|
+
|
|
115
|
+
#### Returns
|
|
116
|
+
|
|
117
|
+
`Promise`\<`void`\>
|
|
118
|
+
|
|
119
|
+
#### Overrides
|
|
120
|
+
|
|
121
|
+
[`BlobStorageStrategyBase`](Class.BlobStorageStrategyBase).[`deleteObject`](Class.BlobStorageStrategyBase#deleteobject)
|
|
122
|
+
|
|
123
|
+
***
|
|
124
|
+
|
|
125
|
+
### downloadObject()
|
|
126
|
+
|
|
127
|
+
```ts
|
|
128
|
+
downloadObject<TData>(request): Promise<IBlobDownloadResult<TData>>;
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Defined in: [strategies/empty/EmptyBlobStorageStrategy.ts:75](https://github.com/RueDeRennes/archipel/blob/main/libs/platform-blob-storage/src/strategies/empty/EmptyBlobStorageStrategy.ts#L75)
|
|
132
|
+
|
|
133
|
+
Downloads a blob object from the storage backend.
|
|
134
|
+
|
|
135
|
+
#### Type Parameters
|
|
136
|
+
|
|
137
|
+
| Type Parameter |
|
|
138
|
+
| ------ |
|
|
139
|
+
| `TData` |
|
|
140
|
+
|
|
141
|
+
#### Parameters
|
|
142
|
+
|
|
143
|
+
| Parameter | Type | Description |
|
|
144
|
+
| ------ | ------ | ------ |
|
|
145
|
+
| `request` | [`IBlobDownloadRequest`](Interface.IBlobDownloadRequest) | The download request describing the object to download. |
|
|
146
|
+
|
|
147
|
+
#### Returns
|
|
148
|
+
|
|
149
|
+
`Promise`\<[`IBlobDownloadResult`](Interface.IBlobDownloadResult)\<`TData`\>\>
|
|
150
|
+
|
|
151
|
+
The download result including payload and metadata.
|
|
152
|
+
|
|
153
|
+
#### Overrides
|
|
154
|
+
|
|
155
|
+
[`BlobStorageStrategyBase`](Class.BlobStorageStrategyBase).[`downloadObject`](Class.BlobStorageStrategyBase#downloadobject)
|
|
156
|
+
|
|
157
|
+
***
|
|
158
|
+
|
|
159
|
+
### uploadObject()
|
|
160
|
+
|
|
161
|
+
```ts
|
|
162
|
+
uploadObject(request): Promise<IBlobUploadResult>;
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Defined in: [strategies/empty/EmptyBlobStorageStrategy.ts:45](https://github.com/RueDeRennes/archipel/blob/main/libs/platform-blob-storage/src/strategies/empty/EmptyBlobStorageStrategy.ts#L45)
|
|
166
|
+
|
|
167
|
+
Uploads a blob object to the storage backend.
|
|
168
|
+
|
|
169
|
+
#### Parameters
|
|
170
|
+
|
|
171
|
+
| Parameter | Type | Description |
|
|
172
|
+
| ------ | ------ | ------ |
|
|
173
|
+
| `request` | [`IBlobUploadRequest`](Interface.IBlobUploadRequest) | The upload request describing the object to upload. |
|
|
174
|
+
|
|
175
|
+
#### Returns
|
|
176
|
+
|
|
177
|
+
`Promise`\<[`IBlobUploadResult`](Interface.IBlobUploadResult)\>
|
|
178
|
+
|
|
179
|
+
The resulting upload metadata.
|
|
180
|
+
|
|
181
|
+
#### Overrides
|
|
182
|
+
|
|
183
|
+
[`BlobStorageStrategyBase`](Class.BlobStorageStrategyBase).[`uploadObject`](Class.BlobStorageStrategyBase#uploadobject)
|
|
@@ -9,6 +9,6 @@ editUrl: false
|
|
|
9
9
|
const AWS_S3_ACCESS_KEY_ID: IConfigKey<string>;
|
|
10
10
|
```
|
|
11
11
|
|
|
12
|
-
Defined in: [strategies/aws-s3/env.ts:
|
|
12
|
+
Defined in: [strategies/aws-s3/env.ts:26](https://github.com/RueDeRennes/archipel/blob/main/libs/platform-blob-storage/src/strategies/aws-s3/env.ts#L26)
|
|
13
13
|
|
|
14
14
|
AWS S3 access key ID.
|
|
@@ -9,6 +9,6 @@ editUrl: false
|
|
|
9
9
|
const AWS_S3_BUCKET: IConfigKey<string>;
|
|
10
10
|
```
|
|
11
11
|
|
|
12
|
-
Defined in: [strategies/aws-s3/env.ts:
|
|
12
|
+
Defined in: [strategies/aws-s3/env.ts:19](https://github.com/RueDeRennes/archipel/blob/main/libs/platform-blob-storage/src/strategies/aws-s3/env.ts#L19)
|
|
13
13
|
|
|
14
14
|
AWS S3 bucket name.
|
|
@@ -9,6 +9,6 @@ editUrl: false
|
|
|
9
9
|
const AWS_S3_CONFIG_ENTRIES: ReadonlyArray<Omit<IConfigRegistryEntry, "module">>;
|
|
10
10
|
```
|
|
11
11
|
|
|
12
|
-
Defined in: [strategies/aws-s3/env.ts:
|
|
12
|
+
Defined in: [strategies/aws-s3/env.ts:47](https://github.com/RueDeRennes/archipel/blob/main/libs/platform-blob-storage/src/strategies/aws-s3/env.ts#L47)
|
|
13
13
|
|
|
14
14
|
Configuration entries required by the AWS S3 blob storage strategy.
|