@breadstone/archipel-mcp 0.0.21 → 0.0.23
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/README.md +32 -13
- package/data/guides/ai-text-generation.md +16 -24
- package/data/guides/cryptography-and-otp.md +2 -2
- package/data/guides/database-setup.md +4 -3
- package/data/guides/email-templates.md +52 -42
- package/data/guides/getting-started.md +6 -6
- package/data/guides/resource-management.md +9 -9
- package/data/packages/platform-authentication/api/Interface.IMfaSubjectUpdate.md +1 -1
- package/data/packages/platform-authentication/api/index.md +1 -1
- package/data/packages/platform-blob-storage/api/Variable.AWS_S3_PROVIDER_OPTIONS.md +1 -1
- package/data/packages/platform-blob-storage/api/Variable.AZURE_BLOB_PROVIDER_OPTIONS.md +1 -1
- package/data/packages/platform-blob-storage/api/Variable.BLOB_PROVIDER.md +1 -1
- package/data/packages/platform-blob-storage/api/Variable.VERCEL_BLOB_PROVIDER_OPTIONS.md +1 -1
- package/data/packages/platform-caching/api/Class.RedisLayeredCache.md +3 -3
- package/data/packages/platform-core/api/Class.ErrorTemplateService.md +1 -1
- package/data/packages/platform-core/api/Class.HostService.md +1 -1
- package/data/packages/platform-core/api/Variable.ID_GENERATOR_TOKEN.md +1 -1
- package/data/packages/platform-core/api/index.md +0 -27
- package/data/packages/platform-core/index.md +7 -4
- package/data/packages/platform-cryptography/api/Class.BcryptService.md +4 -4
- package/data/packages/platform-cryptography/api/Class.OtpService.md +6 -6
- package/data/packages/platform-cryptography/api/Variable.BCRYPT_OPTIONS.md +2 -2
- package/data/packages/platform-cryptography/api/Variable.MAX_BCRYPT_PASSWORD_BYTES.md +1 -1
- package/data/packages/platform-cryptography/api/Variable.MIN_BCRYPT_ROUNDS.md +1 -1
- package/data/packages/platform-cryptography/api/Variable.OTP_OPTIONS.md +2 -2
- package/data/packages/platform-cryptography/api/Variable.OTP_SERVICE_TOKEN.md +2 -2
- package/data/packages/platform-cryptography/api/Variable.TOTP_EPOCH_TOLERANCE.md +1 -1
- package/data/packages/platform-cryptography/api/index.md +3 -3
- package/data/packages/platform-documents/api/Class.BaseDocumentRenderer.md +32 -1
- package/data/packages/platform-documents/api/Class.DocumentEngine.md +4 -4
- package/data/packages/platform-documents/api/Class.DocumentModule.md +2 -2
- package/data/packages/platform-documents/api/Class.DocxDocumentRenderer2.md +333 -0
- package/data/packages/platform-documents/api/Class.PdfDocumentRenderer.md +355 -0
- package/data/packages/platform-documents/api/Interface.IDocumentModuleOptions.md +31 -5
- package/data/packages/platform-documents/api/Variable.DOCUMENT_MODULE_OPTIONS.md +1 -1
- package/data/packages/platform-documents/api/Variable.DOCUMENT_PARSER_TOKEN.md +1 -1
- package/data/packages/platform-documents/api/Variable.DOCUMENT_RENDERER_TOKEN.md +1 -1
- package/data/packages/platform-documents/api/Variable.IMAGE_PROCESSOR_TOKEN.md +1 -1
- package/data/packages/platform-documents/api/index.md +2 -0
- package/data/packages/platform-esigning/api/Class.EsigningClientPort.md +1 -1
- package/data/packages/platform-health/index.md +128 -0
- package/data/packages/platform-mailing/api/Class.DeliveryStrategyBase.md +0 -1
- package/data/packages/platform-mailing/api/Class.MailModule.md +1 -1
- package/data/packages/platform-mailing/api/Class.MailVerificationService.md +1 -1
- package/data/packages/platform-mailing/api/Class.TemplateFetchStrategyBase.md +0 -5
- package/data/packages/platform-mailing/api/Variable.SMTP_CONFIG_ENTRIES.md +14 -0
- package/data/packages/platform-mailing/api/Variable.SMTP_HOST.md +14 -0
- package/data/packages/platform-mailing/api/Variable.SMTP_PASSWORD.md +14 -0
- package/data/packages/platform-mailing/api/Variable.SMTP_PORT.md +14 -0
- package/data/packages/platform-mailing/api/Variable.SMTP_SECURE.md +14 -0
- package/data/packages/platform-mailing/api/Variable.SMTP_USER.md +14 -0
- package/data/packages/platform-mailing/api/index.md +7 -4
- package/data/packages/platform-mapping/index.md +121 -0
- package/data/packages/platform-mcp/api/Variable.MCP_MODULE_OPTIONS.md +1 -1
- package/data/packages/platform-openapi/api/Class.SwaggerMultiDocumentService.md +4 -4
- package/data/packages/platform-resources/index.md +135 -0
- package/data/packages/platform-telemetry/api/Class.OtelSdkHolder.md +1 -1
- package/data/packages/platform-telemetry/api/Variable.TELEMETRY_ENABLED.md +1 -1
- package/data/packages/platform-telemetry/api/Variable.TELEMETRY_FACADE.md +1 -1
- package/data/packages/platform-telemetry/api/Variable.TELEMETRY_OPTIONS.md +1 -1
- package/{src/knowledge/configPattern.js → data/patterns/config-pattern.md} +46 -49
- package/{src/knowledge/dtoPattern.js → data/patterns/dto-pattern.md} +58 -61
- package/{src/knowledge/errorHandlingPattern.js → data/patterns/error-handling-pattern.md} +35 -38
- package/{src/knowledge/guardPattern.js → data/patterns/guard-pattern.md} +35 -38
- package/{src/knowledge/mappingPattern.js → data/patterns/mapping-pattern.md} +43 -144
- package/data/patterns/module-pattern.md +182 -0
- package/data/patterns/query-pattern.md +137 -0
- package/data/patterns/repository-pattern.md +208 -0
- package/{src/knowledge/testingPattern.js → data/patterns/testing-pattern.md} +37 -40
- package/package.json +2 -2
- package/src/PatternsLoader.d.ts +12 -0
- package/src/PatternsLoader.js +65 -0
- package/src/generators/mappingPatternGenerator.d.ts +5 -0
- package/src/generators/mappingPatternGenerator.js +107 -0
- package/src/generators/modulePatternGenerator.d.ts +5 -0
- package/src/generators/modulePatternGenerator.js +107 -0
- package/src/generators/queryPatternGenerator.d.ts +4 -0
- package/src/generators/queryPatternGenerator.js +83 -0
- package/src/generators/repositoryPatternGenerator.d.ts +5 -0
- package/src/generators/repositoryPatternGenerator.js +165 -0
- package/src/main.js +15 -9
- package/src/models/IPatternDoc.d.ts +15 -0
- package/src/models/IPatternDoc.js +3 -0
- package/src/tools/registerGetConfigPatternTool.d.ts +2 -1
- package/src/tools/registerGetConfigPatternTool.js +5 -4
- package/src/tools/registerGetDtoPatternTool.d.ts +2 -1
- package/src/tools/registerGetDtoPatternTool.js +5 -4
- package/src/tools/registerGetErrorHandlingPatternTool.d.ts +2 -1
- package/src/tools/registerGetErrorHandlingPatternTool.js +5 -4
- package/src/tools/registerGetGuardPatternTool.d.ts +2 -1
- package/src/tools/registerGetGuardPatternTool.js +5 -4
- package/src/tools/registerGetMappingPatternTool.d.ts +2 -1
- package/src/tools/registerGetMappingPatternTool.js +6 -5
- package/src/tools/registerGetModulePatternTool.d.ts +2 -1
- package/src/tools/registerGetModulePatternTool.js +6 -5
- package/src/tools/registerGetQueryPatternTool.d.ts +2 -1
- package/src/tools/registerGetQueryPatternTool.js +6 -5
- package/src/tools/registerGetRepositoryPatternTool.d.ts +2 -1
- package/src/tools/registerGetRepositoryPatternTool.js +6 -5
- package/src/tools/registerGetTestingPatternTool.d.ts +2 -1
- package/src/tools/registerGetTestingPatternTool.js +5 -4
- package/data/packages/platform-core/api/Class.BlobResourceStrategy.md +0 -195
- package/data/packages/platform-core/api/Class.EmbeddedResourceStrategy.md +0 -215
- package/data/packages/platform-core/api/Class.FileResourceStrategy.md +0 -192
- package/data/packages/platform-core/api/Class.HealthModule.md +0 -42
- package/data/packages/platform-core/api/Class.HealthOrchestrator.md +0 -64
- package/data/packages/platform-core/api/Class.MappingBuilder.md +0 -110
- package/data/packages/platform-core/api/Class.MappingModule.md +0 -46
- package/data/packages/platform-core/api/Class.MappingNotRegisteredError.md +0 -56
- package/data/packages/platform-core/api/Class.MappingProfileBase.md +0 -52
- package/data/packages/platform-core/api/Class.MappingService.md +0 -284
- package/data/packages/platform-core/api/Class.ResourceManager.md +0 -565
- package/data/packages/platform-core/api/Class.ResourceModule.md +0 -46
- package/data/packages/platform-core/api/Class.TypeMappingNotRegisteredError.md +0 -57
- package/data/packages/platform-core/api/Function.createMappingKey.md +0 -39
- package/data/packages/platform-core/api/Interface.IBlobResourceStrategyConfig.md +0 -28
- package/data/packages/platform-core/api/Interface.IBlobServiceAdapter.md +0 -40
- package/data/packages/platform-core/api/Interface.IFileResourceStrategyConfig.md +0 -72
- package/data/packages/platform-core/api/Interface.IHealthCheckResult.md +0 -46
- package/data/packages/platform-core/api/Interface.IHealthIndicator.md +0 -41
- package/data/packages/platform-core/api/Interface.IMappingBuilder.md +0 -76
- package/data/packages/platform-core/api/Interface.IMappingKey.md +0 -58
- package/data/packages/platform-core/api/Interface.IMappingProfile.md +0 -32
- package/data/packages/platform-core/api/Interface.IResourceManagerConfig.md +0 -89
- package/data/packages/platform-core/api/Interface.IResourceMetadata.md +0 -94
- package/data/packages/platform-core/api/Interface.IResourceResult.md +0 -34
- package/data/packages/platform-core/api/Interface.IResourceStrategy.md +0 -134
- package/data/packages/platform-core/api/Variable.HEALTH_INDICATORS_TOKEN.md +0 -14
- package/data/packages/platform-mailing/api/Class.BlobTemplateFetchStrategy.md +0 -60
- package/data/packages/platform-mailing/api/Class.FileTemplateFetchStrategy.md +0 -58
- package/data/packages/platform-mailing/api/Class.LogDeliveryStrategy.md +0 -71
- package/src/knowledge/configPattern.d.ts +0 -5
- package/src/knowledge/dtoPattern.d.ts +0 -5
- package/src/knowledge/errorHandlingPattern.d.ts +0 -5
- package/src/knowledge/guardPattern.d.ts +0 -5
- package/src/knowledge/mappingPattern.d.ts +0 -6
- package/src/knowledge/modulePattern.d.ts +0 -6
- package/src/knowledge/modulePattern.js +0 -283
- package/src/knowledge/queryPattern.d.ts +0 -6
- package/src/knowledge/queryPattern.js +0 -215
- package/src/knowledge/repositoryPattern.d.ts +0 -6
- package/src/knowledge/repositoryPattern.js +0 -367
- package/src/knowledge/testingPattern.d.ts +0 -5
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Dynamic Module Pattern
|
|
3
|
+
description: NestJS dynamic modules with register(), port/adapter dependency inversion, and ConfigModule integration.
|
|
4
|
+
order: 6
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Archipel Dynamic Module Pattern
|
|
8
|
+
|
|
9
|
+
## Overview
|
|
10
|
+
|
|
11
|
+
Archipel platform libraries use **NestJS dynamic modules** with a `register()` factory
|
|
12
|
+
method and **port/adapter dependency inversion**. Each library defines abstract port
|
|
13
|
+
classes. The consuming application provides concrete implementations at registration time.
|
|
14
|
+
|
|
15
|
+
This pattern keeps platform libraries free of product-specific logic while allowing
|
|
16
|
+
full customization through dependency injection.
|
|
17
|
+
|
|
18
|
+
## Pattern Structure
|
|
19
|
+
|
|
20
|
+
### 1. Abstract Port (Contract)
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
// contracts/FeatureAccessPort.ts
|
|
24
|
+
|
|
25
|
+
export abstract class FeatureAccessPort {
|
|
26
|
+
|
|
27
|
+
public abstract checkAccess(userId: string, featureKey: string): Promise<IFeatureAccessResult>;
|
|
28
|
+
|
|
29
|
+
public abstract recordUsage(userId: string, featureKey: string): Promise<void>;
|
|
30
|
+
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Ports are **abstract classes** (not interfaces) so they can serve as NestJS injection tokens.
|
|
35
|
+
|
|
36
|
+
### 2. Options Interface
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
// interfaces/IPaymentModuleOptions.ts
|
|
40
|
+
|
|
41
|
+
export interface IPaymentModuleOptions {
|
|
42
|
+
/**
|
|
43
|
+
* Concrete implementation of the FeatureAccessPort.
|
|
44
|
+
*/
|
|
45
|
+
featureAccess?: Type<FeatureAccessPort>;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* When true the module is registered globally.
|
|
49
|
+
*/
|
|
50
|
+
isGlobal?: boolean;
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### 3. Dynamic Module with register()
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
// PaymentModule.ts
|
|
58
|
+
import { ConfigModule } from '@breadstone/archipel-platform-core';
|
|
59
|
+
import { type DynamicModule, Module, type Type } from '@nestjs/common';
|
|
60
|
+
|
|
61
|
+
@Module({})
|
|
62
|
+
export class PaymentModule {
|
|
63
|
+
|
|
64
|
+
public static register(options?: IPaymentModuleOptions): DynamicModule {
|
|
65
|
+
const providers = [StripeClient, FeatureGuard, FeatureUsageInterceptor];
|
|
66
|
+
|
|
67
|
+
if (options?.featureAccess) {
|
|
68
|
+
providers.push({
|
|
69
|
+
provide: FeatureAccessPort,
|
|
70
|
+
useClass: options.featureAccess,
|
|
71
|
+
} as never);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
global: options?.isGlobal ?? false,
|
|
76
|
+
module: PaymentModule,
|
|
77
|
+
imports: [ConfigModule.register('platform-payments', PLATFORM_PAYMENTS_CONFIG_ENTRIES)],
|
|
78
|
+
providers: providers,
|
|
79
|
+
exports: [StripeClient, FeatureGuard, FeatureUsageInterceptor],
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### 4. Consumer Registration
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
// In the consuming application:
|
|
90
|
+
@Module({
|
|
91
|
+
imports: [
|
|
92
|
+
PaymentModule.register({
|
|
93
|
+
featureAccess: MyFeatureAccessAdapter,
|
|
94
|
+
isGlobal: true,
|
|
95
|
+
}),
|
|
96
|
+
],
|
|
97
|
+
})
|
|
98
|
+
export class AppModule {}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Variants in the Codebase
|
|
102
|
+
|
|
103
|
+
### Simple: PaymentModule
|
|
104
|
+
|
|
105
|
+
- 1 optional port (`featureAccess`)
|
|
106
|
+
- Optional `isGlobal` flag
|
|
107
|
+
- Config via `ConfigModule.register()`
|
|
108
|
+
|
|
109
|
+
### Complex: AuthModule
|
|
110
|
+
|
|
111
|
+
- 4 required ports (`authSubject`, `mfaSubject`, `sessionPersistence`, `verificationSubject`)
|
|
112
|
+
- 2 optional ports (`socialAuth`, `tokenEnricher`)
|
|
113
|
+
- Conditional strategy registration (social OAuth only when `socialAuth` provided)
|
|
114
|
+
- Imports: PassportModule, JwtModule, MappingModule, EventModule, IdentifierModule
|
|
115
|
+
- Middleware configuration via `configure(consumer)`
|
|
116
|
+
|
|
117
|
+
### Provider-based: BlobModule
|
|
118
|
+
|
|
119
|
+
- Uses `forRoot()` instead of `register()` (global by default)
|
|
120
|
+
- Discriminated union for provider selection: `{ kind: 'Vercel' }` or `{ kind: 'Custom', useClass: ... }`
|
|
121
|
+
- Optional persistence ports (`objectPersistence`, `variantPersistence`)
|
|
122
|
+
|
|
123
|
+
### Infrastructure: DatabaseModule
|
|
124
|
+
|
|
125
|
+
- Both `forRoot()` (global) and `register()` (scoped) variants
|
|
126
|
+
- Factory provider for PrismaService with extensions
|
|
127
|
+
- Health indicator bootstrapping
|
|
128
|
+
|
|
129
|
+
### Async: McpModule
|
|
130
|
+
|
|
131
|
+
- `register(options)` for synchronous configuration
|
|
132
|
+
- `registerAsync({ useFactory, inject })` for async (e.g., reading config at runtime)
|
|
133
|
+
- `registerAsync({ useClass })` for factory class pattern
|
|
134
|
+
- `registerAsync({ useExisting })` for reusing an existing factory
|
|
135
|
+
|
|
136
|
+
## ConfigModule Integration
|
|
137
|
+
|
|
138
|
+
Every library module imports `ConfigModule.register()` to declare its configuration
|
|
139
|
+
dependencies:
|
|
140
|
+
|
|
141
|
+
```typescript
|
|
142
|
+
imports: [ConfigModule.register('platform-payments', PLATFORM_PAYMENTS_CONFIG_ENTRIES)]
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
This registers typed config keys into the global `ConfigRegistry` for validation
|
|
146
|
+
and discovery.
|
|
147
|
+
|
|
148
|
+
## MappingModule Integration
|
|
149
|
+
|
|
150
|
+
Modules that need object mapping import `MappingModule.withProfiles()`:
|
|
151
|
+
|
|
152
|
+
```typescript
|
|
153
|
+
imports: [MappingModule.withProfiles([SessionMappingProfile])]
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## Health Indicator Pattern
|
|
157
|
+
|
|
158
|
+
Infrastructure modules (Database, Blob) register health indicators:
|
|
159
|
+
|
|
160
|
+
```typescript
|
|
161
|
+
providers: [
|
|
162
|
+
BlobHealthIndicator,
|
|
163
|
+
{
|
|
164
|
+
provide: 'BLOB_HEALTH_INDICATOR_BOOTSTRAP',
|
|
165
|
+
useFactory: (orchestrator: HealthOrchestrator, indicator: BlobHealthIndicator) => {
|
|
166
|
+
orchestrator.registerIndicator(indicator);
|
|
167
|
+
return true;
|
|
168
|
+
},
|
|
169
|
+
inject: [HealthOrchestrator, BlobHealthIndicator],
|
|
170
|
+
},
|
|
171
|
+
]
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## Rules
|
|
175
|
+
|
|
176
|
+
1. **Ports are abstract classes** — not interfaces — so they work as DI tokens.
|
|
177
|
+
2. **`register()` for stateless config**, `registerAsync()` when config depends on runtime values.
|
|
178
|
+
3. **`forRoot()` for global singletons** (database, blob storage), `register()` for scoped modules.
|
|
179
|
+
4. **Always import ConfigModule.register()** to declare environment dependencies.
|
|
180
|
+
5. **Optional ports use conditional provider registration** (`if (options?.port)`).
|
|
181
|
+
6. **Required ports are listed in the options interface** without optional markers.
|
|
182
|
+
7. **Exports should list concrete services, guards, interceptors** — not internal implementation details.
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Query Pattern
|
|
3
|
+
description: Repository + Query pattern with IRepositoryQuery, factory functions, transactional queries, and rules.
|
|
4
|
+
order: 7
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Archipel Query Pattern (v1)
|
|
8
|
+
|
|
9
|
+
## Overview
|
|
10
|
+
|
|
11
|
+
In Archipel, data access follows the **Repository + Query** pattern. Repositories never expose
|
|
12
|
+
raw Prisma delegate methods to services. Instead, every data operation is wrapped in a
|
|
13
|
+
**query factory function** that returns an `IRepositoryQuery` object. Services call
|
|
14
|
+
`repository.execute(someQuery(...))` — never `repository.findFirst(...)` directly.
|
|
15
|
+
|
|
16
|
+
## Core Interface
|
|
17
|
+
|
|
18
|
+
```typescript
|
|
19
|
+
export interface IRepositoryQuery<TDelegate, TResult> {
|
|
20
|
+
readonly name: string;
|
|
21
|
+
run(model: TDelegate): Promise<TResult>;
|
|
22
|
+
}
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
- `TDelegate` — the Prisma delegate type (e.g. `Prisma.UserDelegate`)
|
|
26
|
+
- `TResult` — the query's return type
|
|
27
|
+
- `name` — identifier used for logging and debugging
|
|
28
|
+
|
|
29
|
+
## Factory Function
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
import { IRepositoryQuery } from '@breadstone/archipel-platform-database';
|
|
33
|
+
|
|
34
|
+
export function query<TDelegate, TResult>(
|
|
35
|
+
name: string,
|
|
36
|
+
run: (model: TDelegate) => Promise<TResult>,
|
|
37
|
+
): IRepositoryQuery<TDelegate, TResult> {
|
|
38
|
+
return { name, run };
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Type Extraction
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
export type QueryResultType<T> = T extends IRepositoryQuery<unknown, infer R> ? R : never;
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Use this to derive the result type from a query factory without duplicating types:
|
|
49
|
+
```typescript
|
|
50
|
+
type UserProfileResult = QueryResultType<ReturnType<typeof findUserProfileQuery>>;
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## How to Write a Query
|
|
54
|
+
|
|
55
|
+
### Step 1: Define the query factory function
|
|
56
|
+
|
|
57
|
+
Each query is a **named function** that returns an `IRepositoryQuery`. The function may accept
|
|
58
|
+
parameters — these become the query's runtime arguments.
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
import { Prisma } from '@prisma/client';
|
|
62
|
+
import { query } from '@breadstone/archipel-platform-database';
|
|
63
|
+
|
|
64
|
+
export function findUserByEmailQuery(email: string) {
|
|
65
|
+
return query<Prisma.UserDelegate, { id: string; email: string; name: string } | null>(
|
|
66
|
+
'findUserByEmail',
|
|
67
|
+
(model) =>
|
|
68
|
+
model.findFirst({
|
|
69
|
+
where: { email },
|
|
70
|
+
select: { id: true, email: true, name: true },
|
|
71
|
+
}),
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Step 2: Call from a service via repository.execute()
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
// In the service:
|
|
80
|
+
const user = await this._userRepository.execute(findUserByEmailQuery(email));
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Step 3: Extract result type if needed for mapping
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
type FindUserByEmailResult = QueryResultType<ReturnType<typeof findUserByEmailQuery>>;
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Transactional Queries
|
|
90
|
+
|
|
91
|
+
For operations that span multiple models, use `transactionalQuery`:
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
import { Prisma } from '@prisma/client';
|
|
95
|
+
import { transactionalQuery } from '@breadstone/archipel-platform-database';
|
|
96
|
+
|
|
97
|
+
export function createOrderWithItemsQuery(data: { userId: string; items: Array<{ productId: string; quantity: number }> }) {
|
|
98
|
+
return transactionalQuery<{ orderId: string }>(
|
|
99
|
+
'createOrderWithItems',
|
|
100
|
+
async (tx: Prisma.TransactionClient) => {
|
|
101
|
+
const order = await tx.order.create({
|
|
102
|
+
data: { userId: data.userId },
|
|
103
|
+
select: { id: true },
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
await tx.orderItem.createMany({
|
|
107
|
+
data: data.items.map((item) => ({
|
|
108
|
+
orderId: order.id,
|
|
109
|
+
productId: item.productId,
|
|
110
|
+
quantity: item.quantity,
|
|
111
|
+
})),
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
return { orderId: order.id };
|
|
115
|
+
},
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Called via:
|
|
121
|
+
```typescript
|
|
122
|
+
const result = await this._orderRepository.executeTransactional(
|
|
123
|
+
createOrderWithItemsQuery({ userId, items }),
|
|
124
|
+
);
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Rules
|
|
128
|
+
|
|
129
|
+
1. **Never call Prisma delegate methods directly from services.** Always go through `execute(query)`.
|
|
130
|
+
2. **Always select only the fields you need.** Use `select` — never return full records.
|
|
131
|
+
3. **Query names must be descriptive.** Use the pattern: `verbModelByConditionQuery`
|
|
132
|
+
(e.g. `findUserByEmailQuery`, `countOrdersByStatusQuery`, `updateUserNameQuery`).
|
|
133
|
+
4. **One query per file** for non-trivial queries. Small related queries may share a file.
|
|
134
|
+
5. **Type the result explicitly.** The second generic parameter of `query()` should be an inline
|
|
135
|
+
object type or a dedicated interface matching the `select` shape.
|
|
136
|
+
6. **Each query factory is a standalone function** (not a class method). Import `query` from
|
|
137
|
+
`@breadstone/archipel-platform-database`.
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Repository Pattern
|
|
3
|
+
description: RepositoryBase subclasses, DelegateArgs/DelegateReturnTypes helpers, module registration, and service injection.
|
|
4
|
+
order: 8
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Archipel Repository Pattern
|
|
8
|
+
|
|
9
|
+
## Overview
|
|
10
|
+
|
|
11
|
+
Every Prisma model gets a dedicated **Repository class** extending `RepositoryBase`.
|
|
12
|
+
Repositories are the ONLY code that touches Prisma delegates. Services never call
|
|
13
|
+
Prisma directly — they call `repository.execute(someQuery(...))` or
|
|
14
|
+
`repository.executeTransactional(someTxQuery(...))`.
|
|
15
|
+
|
|
16
|
+
## RepositoryBase Signature
|
|
17
|
+
|
|
18
|
+
```typescript
|
|
19
|
+
import { RepositoryBase } from '@breadstone/archipel-platform-database';
|
|
20
|
+
|
|
21
|
+
export abstract class RepositoryBase<
|
|
22
|
+
TDelegate extends Record<RepositoryOperations, (args: any) => Promise<any>>,
|
|
23
|
+
TArgs extends Record<RepositoryOperations, unknown>,
|
|
24
|
+
TReturn extends Record<RepositoryOperations, unknown>,
|
|
25
|
+
TEntity = unknown
|
|
26
|
+
>
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### Generic Parameters
|
|
30
|
+
|
|
31
|
+
| Param | Purpose | Example |
|
|
32
|
+
|-------|---------|---------|
|
|
33
|
+
| `TDelegate` | Prisma delegate type from generated client | `Prisma.UserDelegate` |
|
|
34
|
+
| `TArgs` | Argument shapes for each operation | `Prisma.UserFindManyArgs`, etc. |
|
|
35
|
+
| `TReturn` | Return shapes for each operation | `User`, `User[]`, etc. |
|
|
36
|
+
| `TEntity` | Domain entity interface (optional) | `IUserEntity` |
|
|
37
|
+
|
|
38
|
+
### Helper Types
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
import { DelegateArgs, DelegateReturnTypes } from '@breadstone/archipel-platform-database';
|
|
42
|
+
|
|
43
|
+
// These extract args and return types from a delegate automatically:
|
|
44
|
+
type UserArgs = DelegateArgs<Prisma.UserDelegate>;
|
|
45
|
+
type UserReturns = DelegateReturnTypes<Prisma.UserDelegate>;
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## How to Create a Repository
|
|
49
|
+
|
|
50
|
+
### Step 1: Create the repository class
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
// repositories/UserRepository.ts
|
|
54
|
+
import { Injectable } from '@nestjs/common';
|
|
55
|
+
import { Prisma } from '@prisma/client';
|
|
56
|
+
import {
|
|
57
|
+
DatabaseService,
|
|
58
|
+
DelegateArgs,
|
|
59
|
+
DelegateReturnTypes,
|
|
60
|
+
RepositoryBase,
|
|
61
|
+
} from '@breadstone/archipel-platform-database';
|
|
62
|
+
|
|
63
|
+
@Injectable()
|
|
64
|
+
export class UserRepository extends RepositoryBase<
|
|
65
|
+
Prisma.UserDelegate,
|
|
66
|
+
DelegateArgs<Prisma.UserDelegate>,
|
|
67
|
+
DelegateReturnTypes<Prisma.UserDelegate>,
|
|
68
|
+
IUserEntity
|
|
69
|
+
> {
|
|
70
|
+
|
|
71
|
+
// #region Ctor
|
|
72
|
+
|
|
73
|
+
public constructor(db: DatabaseService) {
|
|
74
|
+
super(db, db.client.user);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// #endregion
|
|
78
|
+
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
The constructor receives `DatabaseService` and passes both the service and the
|
|
83
|
+
Prisma delegate (`db.client.user`) to the base class.
|
|
84
|
+
|
|
85
|
+
### Step 2: Register as a provider in a module
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
@Module({
|
|
89
|
+
imports: [DatabaseModule.register()],
|
|
90
|
+
providers: [UserRepository, UserService],
|
|
91
|
+
exports: [UserService],
|
|
92
|
+
})
|
|
93
|
+
export class UserModule {}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Step 3: Inject into a service
|
|
97
|
+
|
|
98
|
+
```typescript
|
|
99
|
+
@Injectable()
|
|
100
|
+
export class UserService {
|
|
101
|
+
|
|
102
|
+
// #region Fields
|
|
103
|
+
|
|
104
|
+
private readonly _userRepository: UserRepository;
|
|
105
|
+
|
|
106
|
+
// #endregion
|
|
107
|
+
|
|
108
|
+
// #region Ctor
|
|
109
|
+
|
|
110
|
+
public constructor(userRepository: UserRepository) {
|
|
111
|
+
this._userRepository = userRepository;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// #endregion
|
|
115
|
+
|
|
116
|
+
// #region Methods
|
|
117
|
+
|
|
118
|
+
public async findById(id: string): Promise<IUserEntity | null> {
|
|
119
|
+
return this._userRepository.execute(findUserByIdQuery(id));
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
public async findAll(skip = 0, take = 20): Promise<IUserEntity[]> {
|
|
123
|
+
return this._userRepository.execute(findAllUsersQuery(skip, take));
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// #endregion
|
|
127
|
+
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## Key Methods on RepositoryBase
|
|
132
|
+
|
|
133
|
+
### execute(query)
|
|
134
|
+
|
|
135
|
+
Primary method for data access. Delegates to a query object.
|
|
136
|
+
|
|
137
|
+
```typescript
|
|
138
|
+
public execute<TResult>(query: IRepositoryQuery<TDelegate, TResult>): Promise<TResult>
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### executeTransactional(query)
|
|
142
|
+
|
|
143
|
+
For multi-model operations within a transaction.
|
|
144
|
+
|
|
145
|
+
```typescript
|
|
146
|
+
public executeTransactional<TResult>(query: ITransactionalRepositoryQuery<TResult>): Promise<TResult>
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Direct Methods (for legacy or internal use only)
|
|
150
|
+
|
|
151
|
+
The base class also exposes `findUnique`, `findMany`, `findFirst`, `create`,
|
|
152
|
+
`createMany`, `update`, `updateMany`, `upsert`, `delete`, `deleteMany`,
|
|
153
|
+
`aggregate`, `count`, `groupBy`, `merge`.
|
|
154
|
+
|
|
155
|
+
**Important:** New code should use `execute(query)` exclusively.
|
|
156
|
+
Direct method calls are available but violate the query pattern convention.
|
|
157
|
+
|
|
158
|
+
### findMany with Pagination
|
|
159
|
+
|
|
160
|
+
```typescript
|
|
161
|
+
// Direct method (legacy)
|
|
162
|
+
const results = await this._repository.findMany(args, { page: 1, perPage: 20 });
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## Error Handling
|
|
166
|
+
|
|
167
|
+
All Prisma errors are caught by the base class `tryCatch` wrapper and re-thrown
|
|
168
|
+
as `RepositoryError`:
|
|
169
|
+
|
|
170
|
+
- `PrismaClientKnownRequestError` → `RepositoryError` with code `'R_KNOWN'`
|
|
171
|
+
- `PrismaClientUnknownRequestError` → `RepositoryError` with code `'R_UNKNOWN'`
|
|
172
|
+
- Other errors are re-thrown unchanged
|
|
173
|
+
|
|
174
|
+
The global `RepositoryExceptionFilter` catches `RepositoryError` and returns
|
|
175
|
+
HTTP 500 with a structured envelope.
|
|
176
|
+
|
|
177
|
+
## Query Integration
|
|
178
|
+
|
|
179
|
+
Repositories work with query factories from the query pattern:
|
|
180
|
+
|
|
181
|
+
```typescript
|
|
182
|
+
// queries/findUserByIdQuery.ts
|
|
183
|
+
import { Prisma } from '@prisma/client';
|
|
184
|
+
import { query } from '@breadstone/archipel-platform-database';
|
|
185
|
+
|
|
186
|
+
export function findUserByIdQuery(id: string) {
|
|
187
|
+
return query<Prisma.UserDelegate, IUserEntity | null>(
|
|
188
|
+
'findUserById',
|
|
189
|
+
(model) => model.findFirst({
|
|
190
|
+
where: { id },
|
|
191
|
+
select: { id: true, email: true, name: true, createdAt: true },
|
|
192
|
+
}),
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Service usage:
|
|
197
|
+
const user = await this._userRepository.execute(findUserByIdQuery(id));
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
## Rules
|
|
201
|
+
|
|
202
|
+
1. **One repository per Prisma model** — e.g. `UserRepository`, `OrderRepository`.
|
|
203
|
+
2. **Constructor receives `DatabaseService`** and passes the delegate via `db.client.<model>`.
|
|
204
|
+
3. **Use `DelegateArgs` and `DelegateReturnTypes`** helper types for the RepositoryBase generics.
|
|
205
|
+
4. **New code uses `execute(query)`** — never direct calls like `repository.findFirst()` from services.
|
|
206
|
+
5. **Register as provider** in the feature module, import `DatabaseModule.register()`.
|
|
207
|
+
6. **Repositories are `@Injectable()`** — they participate in NestJS DI.
|
|
208
|
+
7. **Domain entity interface as fourth generic** (`TEntity`) is optional but recommended.
|