@breadstone/archipel-mcp 0.0.10 → 0.0.13
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 +361 -0
- package/data/guides/analytics-and-error-tracking.md +189 -0
- package/data/guides/authentication-and-authorization.md +657 -0
- package/data/guides/blob-storage.md +242 -0
- package/data/guides/caching.md +255 -0
- package/data/guides/cryptography-and-otp.md +240 -0
- package/data/guides/database-setup.md +546 -0
- package/data/guides/document-generation.md +174 -0
- package/data/guides/email-delivery.md +196 -0
- package/data/guides/esigning-integration.md +231 -0
- package/data/guides/getting-started.md +351 -0
- package/data/guides/implementing-ports.md +317 -0
- package/data/guides/index.md +62 -0
- package/data/guides/mcp-server.md +222 -0
- package/data/guides/openapi-and-feature-discovery.md +266 -0
- package/data/guides/payments-and-feature-gating.md +244 -0
- package/data/guides/resource-management.md +352 -0
- package/data/guides/telemetry-and-observability.md +190 -0
- package/data/guides/testing.md +319 -0
- package/data/guides/tsdoc-guidelines.md +45 -0
- package/data/packages/platform-database/api/Class.DatabaseModule.md +3 -5
- package/data/packages/platform-database/api/Class.DatabaseService.md +5 -4
- package/data/packages/platform-database/api/Class.PrismaService.md +8 -7
- package/data/packages/platform-database/api/Interface.IDatabaseModuleConfig.md +13 -1
- package/data/packages/platform-database/api/Interface.IPrismaServiceOptions.md +22 -0
- package/data/packages/platform-database/api/Variable.DATABASE_MODULE_CONFIG.md +14 -0
- package/data/packages/platform-database/api/Variable.DB_ACCELERATE_URL.md +14 -0
- package/data/packages/platform-database/api/Variable.DB_AUTO_CONNECTION.md +14 -0
- package/data/packages/platform-database/api/Variable.DB_OPTIMIZE.md +14 -0
- package/data/packages/platform-database/api/Variable.DB_OPTIMIZE_API_KEY.md +14 -0
- package/data/packages/platform-database/api/Variable.PLATFORM_DATABASE_CONFIG_ENTRIES.md +14 -0
- package/data/packages/platform-database/api/index.md +13 -1
- package/data/packages/platform-database/index.md +41 -18
- package/data/packages/platform-openapi/api/Class.SwaggerFeatureDiscovery.md +10 -6
- package/data/packages/platform-telemetry/api/Class.NoopTelemetryFacade.md +10 -10
- package/data/packages/platform-telemetry/api/Interface.ITelemetryFacade.md +1 -1
- package/package.json +1 -1
- package/src/GuidesLoader.d.ts +13 -0
- package/src/GuidesLoader.js +81 -0
- package/src/main.js +36 -198
- package/src/models/IGuideDoc.d.ts +15 -0
- package/src/models/IGuideDoc.js +3 -0
- package/src/tools/registerGetConfigPatternTool.d.ts +5 -0
- package/src/tools/registerGetConfigPatternTool.js +15 -0
- package/src/tools/registerGetDtoPatternTool.d.ts +5 -0
- package/src/tools/registerGetDtoPatternTool.js +15 -0
- package/src/tools/registerGetErrorHandlingPatternTool.d.ts +5 -0
- package/src/tools/registerGetErrorHandlingPatternTool.js +15 -0
- package/src/tools/registerGetGuardPatternTool.d.ts +5 -0
- package/src/tools/registerGetGuardPatternTool.js +15 -0
- package/src/tools/registerGetGuideTool.d.ts +6 -0
- package/src/tools/registerGetGuideTool.js +32 -0
- package/src/tools/registerGetMappingPatternTool.d.ts +5 -0
- package/src/tools/registerGetMappingPatternTool.js +34 -0
- package/src/tools/registerGetModulePatternTool.d.ts +5 -0
- package/src/tools/registerGetModulePatternTool.js +29 -0
- package/src/tools/registerGetPackageDocTool.d.ts +6 -0
- package/src/tools/registerGetPackageDocTool.js +43 -0
- package/src/tools/registerGetQueryPatternTool.d.ts +5 -0
- package/src/tools/registerGetQueryPatternTool.js +29 -0
- package/src/tools/registerGetRepositoryPatternTool.d.ts +5 -0
- package/src/tools/registerGetRepositoryPatternTool.js +29 -0
- package/src/tools/registerGetTestingPatternTool.d.ts +5 -0
- package/src/tools/registerGetTestingPatternTool.js +15 -0
- package/src/tools/registerListGuidesTool.d.ts +6 -0
- package/src/tools/registerListGuidesTool.js +22 -0
- package/src/tools/registerListPackagesTool.d.ts +6 -0
- package/src/tools/registerListPackagesTool.js +20 -0
- package/src/tools/registerSearchDocsTool.d.ts +6 -0
- package/src/tools/registerSearchDocsTool.js +35 -0
- package/src/tools/registerSearchGuidesTool.d.ts +6 -0
- package/src/tools/registerSearchGuidesTool.js +28 -0
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Telemetry & Observability
|
|
3
|
+
description: Add OpenTelemetry-based metrics, tracing, and structured logging to your NestJS application.
|
|
4
|
+
order: 10
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Telemetry & Observability
|
|
8
|
+
|
|
9
|
+
This guide covers adding observability to your application with `platform-telemetry`: setup, metrics recording, distributed tracing, structured logging, and graceful shutdown.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
yarn add @breadstone/archipel-platform-telemetry
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Module Registration
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
import { Module } from '@nestjs/common';
|
|
25
|
+
import { TelemetryModule } from '@breadstone/archipel-platform-telemetry';
|
|
26
|
+
|
|
27
|
+
@Module({
|
|
28
|
+
imports: [
|
|
29
|
+
TelemetryModule.register({
|
|
30
|
+
enabled: true,
|
|
31
|
+
interceptor: {
|
|
32
|
+
operation: true, // auto-instrument controller operations
|
|
33
|
+
},
|
|
34
|
+
}),
|
|
35
|
+
],
|
|
36
|
+
})
|
|
37
|
+
export class AppModule {}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
When `enabled` is `false`, the module provides a no-op facade (`NoopTelemetryFacade`) that silently discards all telemetry calls. This means you can instrument your code once and toggle telemetry via configuration.
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## Configuration
|
|
45
|
+
|
|
46
|
+
```env
|
|
47
|
+
TELEMETRY_ENABLED=true
|
|
48
|
+
TELEMETRY_SERVICE_NAME=my-api
|
|
49
|
+
TELEMETRY_EXPORTER_ENDPOINT=https://otel-collector.example.com:4318
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## Recording Metrics
|
|
55
|
+
|
|
56
|
+
Use `TelemetryFacade` to record application metrics:
|
|
57
|
+
|
|
58
|
+
```typescript
|
|
59
|
+
import { Injectable } from '@nestjs/common';
|
|
60
|
+
import { TelemetryFacade } from '@breadstone/archipel-platform-telemetry';
|
|
61
|
+
|
|
62
|
+
@Injectable()
|
|
63
|
+
export class OrderService {
|
|
64
|
+
private readonly _telemetry: TelemetryFacade;
|
|
65
|
+
|
|
66
|
+
constructor(telemetry: TelemetryFacade) {
|
|
67
|
+
this._telemetry = telemetry;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
public async createOrder(userId: string): Promise<void> {
|
|
71
|
+
const start = Date.now();
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
// ... business logic
|
|
75
|
+
|
|
76
|
+
this._telemetry.recordCounter('domain_command_total', 1, {
|
|
77
|
+
command: 'create_order',
|
|
78
|
+
status: 'success',
|
|
79
|
+
});
|
|
80
|
+
} catch (error) {
|
|
81
|
+
this._telemetry.recordCounter('domain_command_total', 1, {
|
|
82
|
+
command: 'create_order',
|
|
83
|
+
status: 'failure',
|
|
84
|
+
});
|
|
85
|
+
throw error;
|
|
86
|
+
} finally {
|
|
87
|
+
this._telemetry.recordHistogram('order_processing_duration_ms', Date.now() - start, {
|
|
88
|
+
command: 'create_order',
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Recommended Baseline Metrics
|
|
96
|
+
|
|
97
|
+
| Metric | Type | Labels |
|
|
98
|
+
| ----------------------------------------- | --------- | ----------------------------- |
|
|
99
|
+
| `db_query_duration_ms` | Histogram | `model`, `operation` |
|
|
100
|
+
| `cache_hits_total` / `cache_misses_total` | Counter | `cache` |
|
|
101
|
+
| `domain_command_total` | Counter | `command`, `status` |
|
|
102
|
+
| `external_http_request_duration_ms` | Histogram | `service`, `action`, `status` |
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## Structured Logging
|
|
107
|
+
|
|
108
|
+
`TelemetryLoggerService` provides structured logging with automatic correlation:
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
import { Injectable } from '@nestjs/common';
|
|
112
|
+
import { TelemetryLoggerService } from '@breadstone/archipel-platform-telemetry';
|
|
113
|
+
|
|
114
|
+
@Injectable()
|
|
115
|
+
export class PaymentService {
|
|
116
|
+
private readonly _logger: TelemetryLoggerService;
|
|
117
|
+
|
|
118
|
+
constructor(logger: TelemetryLoggerService) {
|
|
119
|
+
this._logger = logger;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
public async processPayment(orderId: string): Promise<void> {
|
|
123
|
+
this._logger.log('Processing payment', { orderId });
|
|
124
|
+
|
|
125
|
+
try {
|
|
126
|
+
// ... payment logic
|
|
127
|
+
this._logger.log('Payment processed successfully', { orderId });
|
|
128
|
+
} catch (error) {
|
|
129
|
+
this._logger.error('Payment processing failed', { orderId, error });
|
|
130
|
+
throw error;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
All log entries automatically include `requestId` for request-level correlation.
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## Automatic Operation Instrumentation
|
|
141
|
+
|
|
142
|
+
When you set `interceptor.operation: true`, every controller method is automatically traced. The interceptor records:
|
|
143
|
+
|
|
144
|
+
- Operation name (controller + method)
|
|
145
|
+
- Duration
|
|
146
|
+
- HTTP status code
|
|
147
|
+
- Error details on failure
|
|
148
|
+
|
|
149
|
+
This gives you endpoint-level performance visibility without manual instrumentation.
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
## Rule Engine
|
|
154
|
+
|
|
155
|
+
`TelemetryRuleEngine` lets you configure sampling and filtering rules:
|
|
156
|
+
|
|
157
|
+
```typescript
|
|
158
|
+
TelemetryModule.register({
|
|
159
|
+
enabled: true,
|
|
160
|
+
interceptor: { operation: true },
|
|
161
|
+
rules: [
|
|
162
|
+
{ pattern: '/health', action: 'drop' }, // Don't trace health checks
|
|
163
|
+
{ pattern: '/api/v1/**', action: 'sample', rate: 0.1 }, // Sample 10% of API traffic
|
|
164
|
+
],
|
|
165
|
+
}),
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
This prevents noisy endpoints (health checks, readiness probes) from overwhelming your telemetry backend.
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
## Graceful Shutdown
|
|
173
|
+
|
|
174
|
+
The module registers a shutdown hook that flushes all pending telemetry data before the process exits. This ensures you don't lose metrics or traces during deployments or scaling events.
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
## Development Tips
|
|
179
|
+
|
|
180
|
+
- Set `TELEMETRY_ENABLED=false` in local development to avoid telemetry overhead.
|
|
181
|
+
- Use the `log` exporter during development to see traces in the console.
|
|
182
|
+
- Add metrics incrementally — start with the baseline metrics and expand as you identify bottlenecks.
|
|
183
|
+
- Include `requestId` in all log entries. The telemetry module handles this automatically for HTTP requests.
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
## Next Steps
|
|
188
|
+
|
|
189
|
+
- Browse the [platform-telemetry API reference](/packages/platform-telemetry/api/) for complete method signatures
|
|
190
|
+
- See the [Testing](/guides/testing) guide for verifying telemetry calls in unit tests
|
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Testing
|
|
3
|
+
description: Testing strategies and patterns for Archipel-powered NestJS applications.
|
|
4
|
+
order: 3
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Testing
|
|
8
|
+
|
|
9
|
+
This guide covers testing patterns for applications that use Archipel packages. It walks through unit testing services, testing port adapters, building in-memory adapters for integration tests, and verifying that modules compose correctly.
|
|
10
|
+
|
|
11
|
+
Archipel uses [Vitest](https://vitest.dev/) as the test runner. All examples follow the Arrange-Act-Assert pattern.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Unit Testing Services
|
|
16
|
+
|
|
17
|
+
When testing services that depend on Archipel ports or other services, mock the dependencies and test business logic in isolation. Don't try to instantiate real modules — that's for integration tests.
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
21
|
+
import { UserService } from './UserService';
|
|
22
|
+
import { type AuthSubjectPort } from '@breadstone/archipel-platform-authentication';
|
|
23
|
+
|
|
24
|
+
describe('UserService', () => {
|
|
25
|
+
let service: UserService;
|
|
26
|
+
let mockAuthSubject: { findById: ReturnType<typeof vi.fn> };
|
|
27
|
+
|
|
28
|
+
beforeEach(() => {
|
|
29
|
+
mockAuthSubject = {
|
|
30
|
+
findById: vi.fn(),
|
|
31
|
+
};
|
|
32
|
+
service = new UserService(mockAuthSubject as unknown as AuthSubjectPort);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('should return user when found', async () => {
|
|
36
|
+
const user = { id: 'user-1', userName: 'alice', email: 'alice@test.com' };
|
|
37
|
+
mockAuthSubject.findById.mockResolvedValue(user);
|
|
38
|
+
|
|
39
|
+
const result = await service.getUser('user-1');
|
|
40
|
+
|
|
41
|
+
expect(result).toEqual(user);
|
|
42
|
+
expect(mockAuthSubject.findById).toHaveBeenCalledWith('user-1');
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('should throw when user not found', async () => {
|
|
46
|
+
mockAuthSubject.findById.mockResolvedValue(null);
|
|
47
|
+
|
|
48
|
+
await expect(service.getUser('missing')).rejects.toThrow();
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
The key principle: mock at the boundary (the port), not inside the implementation. Your test shouldn't know that `AuthSubjectPort` calls Prisma under the hood — it only knows the port interface.
|
|
54
|
+
|
|
55
|
+
### Mocking the MappingService
|
|
56
|
+
|
|
57
|
+
The `MappingService` from `platform-core` is commonly injected in controllers to convert entities to response DTOs. Mock it in unit tests to avoid requiring mapping profile registration:
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
import { MappingService } from '@breadstone/archipel-platform-core';
|
|
61
|
+
|
|
62
|
+
const mockMappingService = {
|
|
63
|
+
map: vi.fn((key, source) => source), // pass-through by default
|
|
64
|
+
} as unknown as MappingService;
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
When the mapping output matters for a specific test, configure the mock:
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
it('should return mapped user response', async () => {
|
|
71
|
+
mockMappingService.map = vi.fn().mockReturnValue({
|
|
72
|
+
id: 'user-1',
|
|
73
|
+
displayName: 'Alice',
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const result = await controller.getUser('user-1');
|
|
77
|
+
|
|
78
|
+
expect(result).toEqual({ id: 'user-1', displayName: 'Alice' });
|
|
79
|
+
expect(mockMappingService.map).toHaveBeenCalledWith(USER_TO_RESPONSE, expect.any(Object));
|
|
80
|
+
});
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Mocking Repositories
|
|
84
|
+
|
|
85
|
+
Repositories extend `RepositoryBase` and use the `execute(query)` pattern internally. In tests, mock the repository's public methods directly — don't try to mock `execute()` or `PrismaService` internals:
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
const mockUserRepository = {
|
|
89
|
+
findById: vi.fn(),
|
|
90
|
+
findByEmail: vi.fn(),
|
|
91
|
+
create: vi.fn(),
|
|
92
|
+
};
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
This keeps your tests decoupled from the data access implementation. If you later switch from Prisma to another ORM or change query structure, service tests stay unchanged.
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## Testing Port Adapters
|
|
100
|
+
|
|
101
|
+
Port adapters translate between your application's data model and the interface the platform library expects. They deserve their own tests because mapping errors are a common source of bugs.
|
|
102
|
+
|
|
103
|
+
```typescript
|
|
104
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
105
|
+
import { PrismaAuthSubjectAdapter } from './PrismaAuthSubjectAdapter';
|
|
106
|
+
|
|
107
|
+
describe('PrismaAuthSubjectAdapter', () => {
|
|
108
|
+
let adapter: PrismaAuthSubjectAdapter;
|
|
109
|
+
let mockPrisma: {
|
|
110
|
+
user: {
|
|
111
|
+
findUnique: ReturnType<typeof vi.fn>;
|
|
112
|
+
findFirst: ReturnType<typeof vi.fn>;
|
|
113
|
+
};
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
beforeEach(() => {
|
|
117
|
+
mockPrisma = {
|
|
118
|
+
user: {
|
|
119
|
+
findUnique: vi.fn(),
|
|
120
|
+
findFirst: vi.fn(),
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
adapter = new PrismaAuthSubjectAdapter(mockPrisma as never);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
describe('findById', () => {
|
|
127
|
+
it('should return null when user does not exist', async () => {
|
|
128
|
+
mockPrisma.user.findUnique.mockResolvedValue(null);
|
|
129
|
+
|
|
130
|
+
const result = await adapter.findById('non-existent');
|
|
131
|
+
|
|
132
|
+
expect(result).toBeNull();
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('should map database record to IAuthSubject', async () => {
|
|
136
|
+
const dbUser = {
|
|
137
|
+
id: 'user-1',
|
|
138
|
+
userName: 'alice',
|
|
139
|
+
email: 'alice@example.com',
|
|
140
|
+
password: '$argon2id$hash',
|
|
141
|
+
isVerified: true,
|
|
142
|
+
isAnonymous: false,
|
|
143
|
+
roles: ['user', 'admin'],
|
|
144
|
+
mfaEnabled: false,
|
|
145
|
+
};
|
|
146
|
+
mockPrisma.user.findUnique.mockResolvedValue(dbUser);
|
|
147
|
+
|
|
148
|
+
const result = await adapter.findById('user-1');
|
|
149
|
+
|
|
150
|
+
expect(result).toEqual({
|
|
151
|
+
id: 'user-1',
|
|
152
|
+
userName: 'alice',
|
|
153
|
+
email: 'alice@example.com',
|
|
154
|
+
password: '$argon2id$hash',
|
|
155
|
+
isVerified: true,
|
|
156
|
+
isAnonymous: false,
|
|
157
|
+
roles: ['user', 'admin'],
|
|
158
|
+
mfaEnabled: false,
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
describe('findByLogin', () => {
|
|
164
|
+
it('should search by both userName and email', async () => {
|
|
165
|
+
mockPrisma.user.findFirst.mockResolvedValue(null);
|
|
166
|
+
|
|
167
|
+
await adapter.findByLogin('alice@example.com');
|
|
168
|
+
|
|
169
|
+
expect(mockPrisma.user.findFirst).toHaveBeenCalledWith({
|
|
170
|
+
where: {
|
|
171
|
+
OR: [{ userName: 'alice@example.com' }, { email: 'alice@example.com' }],
|
|
172
|
+
},
|
|
173
|
+
select: expect.any(Object),
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### Testing Schema Mapping
|
|
181
|
+
|
|
182
|
+
When your database schema doesn't match the port interface directly, your adapter performs a mapping. Test that mapping explicitly — it's where subtle bugs hide:
|
|
183
|
+
|
|
184
|
+
```typescript
|
|
185
|
+
it('should derive isAnonymous from user type field', async () => {
|
|
186
|
+
const dbUser = {
|
|
187
|
+
id: 'u1',
|
|
188
|
+
username: 'guest_42',
|
|
189
|
+
emailAddress: null,
|
|
190
|
+
passwordHash: null,
|
|
191
|
+
emailVerified: false,
|
|
192
|
+
type: 'anonymous',
|
|
193
|
+
userRoles: [{ role: { name: 'guest' } }],
|
|
194
|
+
twoFactorEnabled: false,
|
|
195
|
+
};
|
|
196
|
+
mockPrisma.user.findUnique.mockResolvedValue(dbUser);
|
|
197
|
+
|
|
198
|
+
const result = await adapter.findById('u1');
|
|
199
|
+
|
|
200
|
+
expect(result?.isAnonymous).toBe(true);
|
|
201
|
+
expect(result?.roles).toEqual(['guest']);
|
|
202
|
+
});
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
## In-Memory Adapters
|
|
208
|
+
|
|
209
|
+
For integration tests where you need working ports without a real database, create in-memory implementations. They're fast, deterministic, and let you seed exactly the state you need for each test.
|
|
210
|
+
|
|
211
|
+
```typescript
|
|
212
|
+
import { Injectable } from '@nestjs/common';
|
|
213
|
+
import { AuthSubjectPort, type IAuthSubject } from '@breadstone/archipel-platform-authentication';
|
|
214
|
+
|
|
215
|
+
@Injectable()
|
|
216
|
+
export class InMemoryAuthSubjectAdapter extends AuthSubjectPort {
|
|
217
|
+
private readonly _subjects = new Map<string, IAuthSubject>();
|
|
218
|
+
|
|
219
|
+
public seed(subjects: IAuthSubject[]): void {
|
|
220
|
+
this._subjects.clear();
|
|
221
|
+
subjects.forEach((s) => this._subjects.set(s.id, s));
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
public async findById(id: string): Promise<IAuthSubject | null> {
|
|
225
|
+
return this._subjects.get(id) ?? null;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
public async findByLogin(login: string): Promise<IAuthSubject | null> {
|
|
229
|
+
return [...this._subjects.values()].find((s) => s.userName === login || s.email === login) ?? null;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
public async findAnonymous(userName: string): Promise<IAuthSubject | null> {
|
|
233
|
+
return [...this._subjects.values()].find((s) => s.userName === userName && s.isAnonymous) ?? null;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
This pattern works for any port. Store state in a `Map`, expose a `seed()` method for setup, and implement the port interface. Your tests run in milliseconds without touching a database.
|
|
239
|
+
|
|
240
|
+
---
|
|
241
|
+
|
|
242
|
+
## Integration Testing with Module Composition
|
|
243
|
+
|
|
244
|
+
To verify that modules wire together correctly — that all providers resolve, ports are bound to the right adapters, and services function end-to-end — bootstrap a minimal NestJS test module:
|
|
245
|
+
|
|
246
|
+
```typescript
|
|
247
|
+
import { Test } from '@nestjs/testing';
|
|
248
|
+
import { AuthModule, AuthTokenService, AuthSubjectPort } from '@breadstone/archipel-platform-authentication';
|
|
249
|
+
import { ConfigModule } from '@breadstone/archipel-platform-core';
|
|
250
|
+
import { InMemoryAuthSubjectAdapter } from './InMemoryAuthSubjectAdapter';
|
|
251
|
+
import { InMemoryMfaSubjectAdapter } from './InMemoryMfaSubjectAdapter';
|
|
252
|
+
import { InMemorySessionAdapter } from './InMemorySessionAdapter';
|
|
253
|
+
import { InMemoryVerificationAdapter } from './InMemoryVerificationAdapter';
|
|
254
|
+
|
|
255
|
+
describe('Auth module integration', () => {
|
|
256
|
+
let authTokenService: AuthTokenService;
|
|
257
|
+
let authSubjectAdapter: InMemoryAuthSubjectAdapter;
|
|
258
|
+
|
|
259
|
+
beforeEach(async () => {
|
|
260
|
+
const module = await Test.createTestingModule({
|
|
261
|
+
imports: [
|
|
262
|
+
ConfigModule.register('test', TEST_CONFIG_ENTRIES),
|
|
263
|
+
AuthModule.register({
|
|
264
|
+
authSubject: InMemoryAuthSubjectAdapter,
|
|
265
|
+
mfaSubject: InMemoryMfaSubjectAdapter,
|
|
266
|
+
sessionPersistence: InMemorySessionAdapter,
|
|
267
|
+
verificationSubject: InMemoryVerificationAdapter,
|
|
268
|
+
}),
|
|
269
|
+
],
|
|
270
|
+
}).compile();
|
|
271
|
+
|
|
272
|
+
authTokenService = module.get(AuthTokenService);
|
|
273
|
+
authSubjectAdapter = module.get(AuthSubjectPort) as InMemoryAuthSubjectAdapter;
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
it('should resolve AuthTokenService with all dependencies', () => {
|
|
277
|
+
expect(authTokenService).toBeDefined();
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
it('should generate a valid JWT for a seeded user', async () => {
|
|
281
|
+
authSubjectAdapter.seed([
|
|
282
|
+
{
|
|
283
|
+
id: 'user-1',
|
|
284
|
+
userName: 'alice',
|
|
285
|
+
email: 'alice@test.com',
|
|
286
|
+
password: '$argon2id$hash',
|
|
287
|
+
isVerified: true,
|
|
288
|
+
isAnonymous: false,
|
|
289
|
+
roles: ['user'],
|
|
290
|
+
mfaEnabled: false,
|
|
291
|
+
},
|
|
292
|
+
]);
|
|
293
|
+
|
|
294
|
+
const token = await authTokenService.signToken({
|
|
295
|
+
id: 'user-1',
|
|
296
|
+
email: 'alice@test.com',
|
|
297
|
+
roles: ['user'],
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
expect(token).toBeDefined();
|
|
301
|
+
expect(typeof token).toBe('string');
|
|
302
|
+
});
|
|
303
|
+
});
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
This catches wiring errors early. If you forget to register a required port, mistype an adapter class, or have a circular dependency, `Test.createTestingModule(...).compile()` throws immediately with a clear error message.
|
|
307
|
+
|
|
308
|
+
---
|
|
309
|
+
|
|
310
|
+
## Pre-Release Checklist
|
|
311
|
+
|
|
312
|
+
Before shipping a new Archipel integration, verify:
|
|
313
|
+
|
|
314
|
+
- [ ] All required port adapters have unit tests covering happy path and null/error cases
|
|
315
|
+
- [ ] Schema mapping is tested explicitly when database fields differ from port interfaces
|
|
316
|
+
- [ ] Module composition boots without missing provider errors (integration test)
|
|
317
|
+
- [ ] Health indicators report correct status for connected infrastructure
|
|
318
|
+
- [ ] Error responses use documented error shapes, not raw NestJS exceptions
|
|
319
|
+
- [ ] Required environment variables fail fast at startup when missing
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: API Documentation Standards
|
|
3
|
+
description: Documentation standards for stable Archipel public APIs and generated reference pages.
|
|
4
|
+
order: 5
|
|
5
|
+
tags: [guides, docs, api, standards]
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# API Documentation Standards
|
|
9
|
+
|
|
10
|
+
Use these standards for package APIs that are consumed by application teams.
|
|
11
|
+
|
|
12
|
+
## What must be documented
|
|
13
|
+
|
|
14
|
+
For every exported public symbol:
|
|
15
|
+
|
|
16
|
+
- purpose and intended usage,
|
|
17
|
+
- input/output contract,
|
|
18
|
+
- failure behavior,
|
|
19
|
+
- minimal usage example when non-obvious.
|
|
20
|
+
|
|
21
|
+
## Consumer-first writing rules
|
|
22
|
+
|
|
23
|
+
- Explain **what integrators need to do**, not repository internals.
|
|
24
|
+
- Mark required vs optional integration points explicitly.
|
|
25
|
+
- Keep examples small and runnable.
|
|
26
|
+
- Avoid architecture jargon without practical effect.
|
|
27
|
+
|
|
28
|
+
## Recommended structure for package overview pages
|
|
29
|
+
|
|
30
|
+
1. Capability summary
|
|
31
|
+
2. Installation/import information
|
|
32
|
+
3. Required ports/adapters
|
|
33
|
+
4. Optional extension points
|
|
34
|
+
5. Minimal registration example
|
|
35
|
+
6. Link to generated API reference
|
|
36
|
+
|
|
37
|
+
## Stability signaling
|
|
38
|
+
|
|
39
|
+
Document stability expectations clearly:
|
|
40
|
+
|
|
41
|
+
- `Stable`: safe for long-lived integrations
|
|
42
|
+
- `Evolving`: may receive additive changes
|
|
43
|
+
- `Experimental`: breaking changes possible
|
|
44
|
+
|
|
45
|
+
If a symbol is internal, do not expose it from the package barrel.
|
|
@@ -5,9 +5,7 @@ editUrl: false
|
|
|
5
5
|
---
|
|
6
6
|
# Class: DatabaseModule
|
|
7
7
|
|
|
8
|
-
Defined in: [DatabaseModule.ts:
|
|
9
|
-
|
|
10
|
-
Represents the database module.
|
|
8
|
+
Defined in: [DatabaseModule.ts:62](https://github.com/RueDeRennes/archipel/blob/main/libs/platform-database/src/DatabaseModule.ts#L62)
|
|
11
9
|
|
|
12
10
|
## Constructors
|
|
13
11
|
|
|
@@ -29,7 +27,7 @@ new DatabaseModule(): DatabaseModule;
|
|
|
29
27
|
static forRoot(config?): DynamicModule;
|
|
30
28
|
```
|
|
31
29
|
|
|
32
|
-
Defined in: [DatabaseModule.ts:
|
|
30
|
+
Defined in: [DatabaseModule.ts:71](https://github.com/RueDeRennes/archipel/blob/main/libs/platform-database/src/DatabaseModule.ts#L71)
|
|
33
31
|
|
|
34
32
|
Registers the `DatabaseModule` with global middlewares.
|
|
35
33
|
|
|
@@ -53,7 +51,7 @@ A `DynamicModule` that can be imported globally
|
|
|
53
51
|
static register(config?): DynamicModule;
|
|
54
52
|
```
|
|
55
53
|
|
|
56
|
-
Defined in: [DatabaseModule.ts:
|
|
54
|
+
Defined in: [DatabaseModule.ts:91](https://github.com/RueDeRennes/archipel/blob/main/libs/platform-database/src/DatabaseModule.ts#L91)
|
|
57
55
|
|
|
58
56
|
Registers the `DatabaseModule` with dynamic middlewares.
|
|
59
57
|
|
|
@@ -24,7 +24,7 @@ Represents the database service.
|
|
|
24
24
|
### Constructor
|
|
25
25
|
|
|
26
26
|
```ts
|
|
27
|
-
new DatabaseService(configService): DatabaseService;
|
|
27
|
+
new DatabaseService(configService, options?): DatabaseService;
|
|
28
28
|
```
|
|
29
29
|
|
|
30
30
|
Defined in: [services/DatabaseService.ts:24](https://github.com/RueDeRennes/archipel/blob/main/libs/platform-database/src/services/DatabaseService.ts#L24)
|
|
@@ -36,6 +36,7 @@ Constructs a new instance of the `DatabaseService` class.
|
|
|
36
36
|
| Parameter | Type |
|
|
37
37
|
| ------ | ------ |
|
|
38
38
|
| `configService` | `ConfigService` |
|
|
39
|
+
| `options?` | [`IPrismaServiceOptions`](Interface.IPrismaServiceOptions) |
|
|
39
40
|
|
|
40
41
|
#### Returns
|
|
41
42
|
|
|
@@ -53,7 +54,7 @@ Constructs a new instance of the `DatabaseService` class.
|
|
|
53
54
|
onModuleDestroy(): Promise<void>;
|
|
54
55
|
```
|
|
55
56
|
|
|
56
|
-
Defined in: [services/PrismaService.ts:
|
|
57
|
+
Defined in: [services/PrismaService.ts:91](https://github.com/RueDeRennes/archipel/blob/main/libs/platform-database/src/services/PrismaService.ts#L91)
|
|
57
58
|
|
|
58
59
|
#### Returns
|
|
59
60
|
|
|
@@ -71,7 +72,7 @@ Defined in: [services/PrismaService.ts:80](https://github.com/RueDeRennes/archip
|
|
|
71
72
|
onModuleInit(): Promise<void>;
|
|
72
73
|
```
|
|
73
74
|
|
|
74
|
-
Defined in: [services/PrismaService.ts:
|
|
75
|
+
Defined in: [services/PrismaService.ts:79](https://github.com/RueDeRennes/archipel/blob/main/libs/platform-database/src/services/PrismaService.ts#L79)
|
|
75
76
|
|
|
76
77
|
#### Returns
|
|
77
78
|
|
|
@@ -149,7 +150,7 @@ Executes a transactional callback (extended overload missing in base typing wrap
|
|
|
149
150
|
withExtensions(): PrismaService;
|
|
150
151
|
```
|
|
151
152
|
|
|
152
|
-
Defined in: [services/PrismaService.ts:
|
|
153
|
+
Defined in: [services/PrismaService.ts:55](https://github.com/RueDeRennes/archipel/blob/main/libs/platform-database/src/services/PrismaService.ts#L55)
|
|
153
154
|
|
|
154
155
|
#### Returns
|
|
155
156
|
|
|
@@ -5,7 +5,7 @@ editUrl: false
|
|
|
5
5
|
---
|
|
6
6
|
# Class: PrismaService
|
|
7
7
|
|
|
8
|
-
Defined in: [services/PrismaService.ts:
|
|
8
|
+
Defined in: [services/PrismaService.ts:27](https://github.com/RueDeRennes/archipel/blob/main/libs/platform-database/src/services/PrismaService.ts#L27)
|
|
9
9
|
|
|
10
10
|
Represents the prisma service.
|
|
11
11
|
|
|
@@ -33,18 +33,19 @@ Represents the prisma service.
|
|
|
33
33
|
### Constructor
|
|
34
34
|
|
|
35
35
|
```ts
|
|
36
|
-
new PrismaService(configService): PrismaService;
|
|
36
|
+
new PrismaService(configService, options?): PrismaService;
|
|
37
37
|
```
|
|
38
38
|
|
|
39
|
-
Defined in: [services/PrismaService.ts:
|
|
39
|
+
Defined in: [services/PrismaService.ts:41](https://github.com/RueDeRennes/archipel/blob/main/libs/platform-database/src/services/PrismaService.ts#L41)
|
|
40
40
|
|
|
41
|
-
Constructs a new instance of the `
|
|
41
|
+
Constructs a new instance of the `PrismaService` class.
|
|
42
42
|
|
|
43
43
|
#### Parameters
|
|
44
44
|
|
|
45
45
|
| Parameter | Type |
|
|
46
46
|
| ------ | ------ |
|
|
47
47
|
| `configService` | `ConfigService` |
|
|
48
|
+
| `options?` | [`IPrismaServiceOptions`](Interface.IPrismaServiceOptions) |
|
|
48
49
|
|
|
49
50
|
#### Returns
|
|
50
51
|
|
|
@@ -64,7 +65,7 @@ PrismaClient.constructor
|
|
|
64
65
|
onModuleDestroy(): Promise<void>;
|
|
65
66
|
```
|
|
66
67
|
|
|
67
|
-
Defined in: [services/PrismaService.ts:
|
|
68
|
+
Defined in: [services/PrismaService.ts:91](https://github.com/RueDeRennes/archipel/blob/main/libs/platform-database/src/services/PrismaService.ts#L91)
|
|
68
69
|
|
|
69
70
|
#### Returns
|
|
70
71
|
|
|
@@ -84,7 +85,7 @@ OnModuleDestroy.onModuleDestroy
|
|
|
84
85
|
onModuleInit(): Promise<void>;
|
|
85
86
|
```
|
|
86
87
|
|
|
87
|
-
Defined in: [services/PrismaService.ts:
|
|
88
|
+
Defined in: [services/PrismaService.ts:79](https://github.com/RueDeRennes/archipel/blob/main/libs/platform-database/src/services/PrismaService.ts#L79)
|
|
88
89
|
|
|
89
90
|
#### Returns
|
|
90
91
|
|
|
@@ -104,7 +105,7 @@ OnModuleInit.onModuleInit
|
|
|
104
105
|
withExtensions(): PrismaService;
|
|
105
106
|
```
|
|
106
107
|
|
|
107
|
-
Defined in: [services/PrismaService.ts:
|
|
108
|
+
Defined in: [services/PrismaService.ts:55](https://github.com/RueDeRennes/archipel/blob/main/libs/platform-database/src/services/PrismaService.ts#L55)
|
|
108
109
|
|
|
109
110
|
#### Returns
|
|
110
111
|
|