nest-hex 0.2.0 → 0.3.2
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 +155 -267
- package/dist/src/cli/bin.js +2644 -0
- package/dist/src/cli/commands/index.d.ts +23 -0
- package/dist/src/cli/commands/index.js +2609 -0
- package/dist/src/cli/config/defaults.d.ts +74 -0
- package/dist/src/cli/config/defaults.js +76 -0
- package/dist/src/cli/config/define-config.d.ts +74 -0
- package/dist/src/cli/config/define-config.js +62 -0
- package/dist/src/cli/config/loader.d.ts +74 -0
- package/dist/src/cli/config/loader.js +106 -0
- package/dist/src/cli/config/validator.d.ts +81 -0
- package/dist/src/cli/config/validator.js +108 -0
- package/dist/src/cli/generators/adapter.generator.d.ts +235 -0
- package/dist/src/cli/generators/adapter.generator.js +377 -0
- package/dist/src/cli/generators/base.generator.d.ts +190 -0
- package/dist/src/cli/generators/base.generator.js +312 -0
- package/dist/src/cli/generators/index.d.ts +264 -0
- package/dist/src/cli/generators/index.js +467 -0
- package/dist/src/cli/generators/port.generator.d.ts +211 -0
- package/dist/src/cli/generators/port.generator.js +364 -0
- package/dist/src/cli/generators/service.generator.d.ts +208 -0
- package/dist/src/cli/generators/service.generator.js +340 -0
- package/dist/src/cli/index.d.ts +74 -0
- package/dist/src/cli/index.js +69 -0
- package/dist/src/cli/types/config.types.d.ts +73 -0
- package/dist/src/cli/types/config.types.js +56 -0
- package/dist/src/cli/types/generator.types.d.ts +46 -0
- package/dist/src/cli/types/generator.types.js +56 -0
- package/dist/src/cli/types/index.d.ts +121 -0
- package/dist/src/cli/types/index.js +56 -0
- package/dist/src/cli/types/template.types.d.ts +28 -0
- package/dist/src/cli/types/template.types.js +56 -0
- package/dist/src/cli/ui/components/index.d.ts +97 -0
- package/dist/src/cli/ui/components/index.js +1507 -0
- package/dist/src/cli/utils/file-writer.d.ts +17 -0
- package/dist/src/cli/utils/file-writer.js +100 -0
- package/dist/src/cli/utils/linter-detector.d.ts +12 -0
- package/dist/src/cli/utils/linter-detector.js +128 -0
- package/dist/src/cli/utils/linter-runner.d.ts +17 -0
- package/dist/src/cli/utils/linter-runner.js +101 -0
- package/dist/src/cli/utils/name-transformer.d.ts +18 -0
- package/dist/src/cli/utils/name-transformer.js +90 -0
- package/dist/src/cli/utils/path-resolver.d.ts +5 -0
- package/dist/src/cli/utils/path-resolver.js +78 -0
- package/dist/src/cli/utils/port-scanner.d.ts +93 -0
- package/dist/src/cli/utils/port-scanner.js +104 -0
- package/dist/src/cli/utils/template-renderer.d.ts +30 -0
- package/dist/src/cli/utils/template-renderer.js +95 -0
- package/dist/{index.d.ts → src/index.d.ts} +60 -30
- package/dist/{index.js → src/index.js} +45 -13
- package/package.json +10 -10
package/README.md
CHANGED
|
@@ -1,29 +1,28 @@
|
|
|
1
1
|
# nest-hex
|
|
2
2
|
|
|
3
|
-
> A tiny, **class-based**, **NestJS-native**
|
|
3
|
+
> A tiny, **class-based**, **NestJS-native** library for building **pluggable adapters** following the Ports & Adapters (Hexagonal Architecture) pattern with minimal boilerplate.
|
|
4
4
|
|
|
5
5
|
[](https://opensource.org/licenses/MIT)
|
|
6
6
|
|
|
7
|
-
##
|
|
7
|
+
## What is nest-hex?
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
**nest-hex** eliminates boilerplate when building NestJS applications with the Ports & Adapters (Hexagonal Architecture) pattern. It provides decorators and base classes that handle all the repetitive wiring, letting you focus on business logic.
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
- Aliasing port tokens to implementations (`useExisting`)
|
|
13
|
-
- Exporting only the port token (not provider objects)
|
|
14
|
-
- Supporting both `register()` and `registerAsync()` patterns
|
|
15
|
-
- Keeping the app responsible for configuration (no `process.env` in libraries)
|
|
11
|
+
### Why Hexagonal Architecture?
|
|
16
12
|
|
|
17
|
-
|
|
13
|
+
- 🧪 **Testable** - Mock infrastructure easily, test business logic in isolation
|
|
14
|
+
- 🔌 **Swappable** - Switch from S3 to Azure Blob Storage without touching domain code
|
|
15
|
+
- 🎯 **Clean** - Keep business logic free of infrastructure concerns
|
|
16
|
+
- 🌍 **Flexible** - Use different adapters for dev, test, and production
|
|
18
17
|
|
|
19
18
|
## Features
|
|
20
19
|
|
|
21
|
-
- 🎯 **Declarative
|
|
22
|
-
- 🏗️ **Class-based
|
|
23
|
-
- 🔒 **Type-safe
|
|
24
|
-
- ⚡ **Zero runtime overhead
|
|
25
|
-
- 📦 **Tiny
|
|
26
|
-
-
|
|
20
|
+
- 🎯 **Declarative** - Declare port tokens and implementations once using `@Adapter({ portToken, implementation })`
|
|
21
|
+
- 🏗️ **Class-based** - Standard NestJS dynamic modules, no function factories
|
|
22
|
+
- 🔒 **Type-safe** - Compile-time proof that adapters provide the correct port tokens
|
|
23
|
+
- ⚡ **Zero runtime overhead** - Uses TypeScript decorators and metadata
|
|
24
|
+
- 📦 **Tiny** - Core library under 1KB minified
|
|
25
|
+
- 🛠️ **Powerful CLI** - Generate ports, adapters, and services instantly
|
|
27
26
|
|
|
28
27
|
## Installation
|
|
29
28
|
|
|
@@ -37,8 +36,7 @@ pnpm add nest-hex
|
|
|
37
36
|
bun add nest-hex
|
|
38
37
|
```
|
|
39
38
|
|
|
40
|
-
|
|
41
|
-
|
|
39
|
+
**Peer dependencies:**
|
|
42
40
|
```bash
|
|
43
41
|
npm install @nestjs/common @nestjs/core reflect-metadata
|
|
44
42
|
```
|
|
@@ -49,11 +47,11 @@ npm install @nestjs/common @nestjs/core reflect-metadata
|
|
|
49
47
|
|
|
50
48
|
```typescript
|
|
51
49
|
// storage.port.ts
|
|
52
|
-
export const STORAGE_PORT = Symbol('STORAGE_PORT')
|
|
50
|
+
export const STORAGE_PORT = Symbol('STORAGE_PORT')
|
|
53
51
|
|
|
54
52
|
export interface StoragePort {
|
|
55
|
-
upload(
|
|
56
|
-
download(key: string): Promise<Buffer
|
|
53
|
+
upload(key: string, data: Buffer): Promise<string>
|
|
54
|
+
download(key: string): Promise<Buffer>
|
|
57
55
|
}
|
|
58
56
|
```
|
|
59
57
|
|
|
@@ -61,103 +59,116 @@ export interface StoragePort {
|
|
|
61
59
|
|
|
62
60
|
```typescript
|
|
63
61
|
// s3.adapter.ts
|
|
64
|
-
import { Injectable } from '@nestjs/common'
|
|
65
|
-
import { Adapter
|
|
66
|
-
import { STORAGE_PORT, type StoragePort } from './storage.port'
|
|
62
|
+
import { Injectable } from '@nestjs/common'
|
|
63
|
+
import { Adapter } from 'nest-hex'
|
|
64
|
+
import { STORAGE_PORT, type StoragePort } from './storage.port'
|
|
67
65
|
|
|
68
66
|
// Implementation service
|
|
69
67
|
@Injectable()
|
|
70
|
-
class
|
|
71
|
-
|
|
68
|
+
class S3Service implements StoragePort {
|
|
69
|
+
constructor(private options: { bucket: string; region: string }) {}
|
|
70
|
+
|
|
71
|
+
async upload(key: string, data: Buffer): Promise<string> {
|
|
72
72
|
// AWS S3 upload logic here
|
|
73
|
-
return
|
|
73
|
+
return `https://s3.amazonaws.com/${this.options.bucket}/${key}`
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
-
async download(key: string) {
|
|
76
|
+
async download(key: string): Promise<Buffer> {
|
|
77
77
|
// AWS S3 download logic here
|
|
78
|
-
return Buffer.from('file contents')
|
|
78
|
+
return Buffer.from('file contents')
|
|
79
79
|
}
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
-
// Adapter configuration
|
|
83
|
-
interface S3Options {
|
|
84
|
-
bucket: string;
|
|
85
|
-
region: string;
|
|
86
|
-
accessKeyId?: string;
|
|
87
|
-
secretAccessKey?: string;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
82
|
// Adapter module - single decorator declares everything!
|
|
91
|
-
@
|
|
92
|
-
|
|
93
|
-
implementation:
|
|
83
|
+
@Adapter({
|
|
84
|
+
portToken: STORAGE_PORT,
|
|
85
|
+
implementation: S3Service
|
|
94
86
|
})
|
|
95
|
-
export class S3Adapter extends
|
|
87
|
+
export class S3Adapter extends AdapterBase<{ bucket: string; region: string }> {}
|
|
96
88
|
```
|
|
97
89
|
|
|
98
|
-
### 3. Create a
|
|
90
|
+
### 3. Create a Domain Service
|
|
99
91
|
|
|
100
92
|
```typescript
|
|
101
|
-
//
|
|
102
|
-
import { Injectable
|
|
103
|
-
import { InjectPort
|
|
104
|
-
import { STORAGE_PORT, type StoragePort } from './storage.port'
|
|
93
|
+
// file.service.ts
|
|
94
|
+
import { Injectable } from '@nestjs/common'
|
|
95
|
+
import { InjectPort } from 'nest-hex'
|
|
96
|
+
import { STORAGE_PORT, type StoragePort } from './storage.port'
|
|
105
97
|
|
|
106
|
-
// Domain service that uses the port
|
|
107
98
|
@Injectable()
|
|
108
|
-
export class
|
|
99
|
+
export class FileService {
|
|
109
100
|
constructor(
|
|
110
101
|
@InjectPort(STORAGE_PORT)
|
|
111
|
-
private readonly storage: StoragePort
|
|
102
|
+
private readonly storage: StoragePort
|
|
112
103
|
) {}
|
|
113
104
|
|
|
114
|
-
async uploadUserAvatar(userId: string, image: Buffer) {
|
|
115
|
-
const key = `avatars/${userId}.jpg
|
|
116
|
-
return this.storage.upload(
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
async downloadUserAvatar(userId: string) {
|
|
120
|
-
const key = `avatars/${userId}.jpg`;
|
|
121
|
-
return this.storage.download(key);
|
|
105
|
+
async uploadUserAvatar(userId: string, image: Buffer): Promise<string> {
|
|
106
|
+
const key = `avatars/${userId}.jpg`
|
|
107
|
+
return this.storage.upload(key, image)
|
|
122
108
|
}
|
|
123
109
|
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### 4. Create a Port Module
|
|
113
|
+
|
|
114
|
+
```typescript
|
|
115
|
+
// file.module.ts
|
|
116
|
+
import { Module } from '@nestjs/common'
|
|
117
|
+
import { PortModule } from 'nest-hex'
|
|
118
|
+
import { FileService } from './file.service'
|
|
124
119
|
|
|
125
|
-
// Port module that accepts any adapter
|
|
126
120
|
@Module({})
|
|
127
|
-
export class
|
|
121
|
+
export class FileModule extends PortModule<typeof FileService> {}
|
|
128
122
|
```
|
|
129
123
|
|
|
130
|
-
###
|
|
124
|
+
### 5. Wire It Up
|
|
131
125
|
|
|
132
126
|
```typescript
|
|
133
127
|
// app.module.ts
|
|
134
|
-
import { Module } from '@nestjs/common'
|
|
135
|
-
import {
|
|
136
|
-
import S3Adapter from './
|
|
128
|
+
import { Module } from '@nestjs/common'
|
|
129
|
+
import { FileModule } from './file.module'
|
|
130
|
+
import { S3Adapter } from './s3.adapter'
|
|
137
131
|
|
|
138
132
|
@Module({
|
|
139
133
|
imports: [
|
|
140
|
-
|
|
134
|
+
FileModule.register({
|
|
141
135
|
adapter: S3Adapter.register({
|
|
142
|
-
bucket: 'my-
|
|
143
|
-
region: 'us-east-1'
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
}),
|
|
148
|
-
],
|
|
136
|
+
bucket: process.env.S3_BUCKET || 'my-bucket',
|
|
137
|
+
region: process.env.AWS_REGION || 'us-east-1'
|
|
138
|
+
})
|
|
139
|
+
})
|
|
140
|
+
]
|
|
149
141
|
})
|
|
150
142
|
export class AppModule {}
|
|
151
143
|
```
|
|
152
144
|
|
|
153
145
|
That's it! You now have a fully type-safe, pluggable storage adapter. 🎉
|
|
154
146
|
|
|
147
|
+
## CLI
|
|
148
|
+
|
|
149
|
+
Generate ports, adapters, and services instantly with the built-in CLI:
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
# Initialize configuration
|
|
153
|
+
npx nest-hex init
|
|
154
|
+
|
|
155
|
+
# Generate a port (domain interface)
|
|
156
|
+
npx nest-hex generate port ObjectStorage
|
|
157
|
+
|
|
158
|
+
# Generate an adapter for the port
|
|
159
|
+
npx nest-hex generate adapter S3 --port ObjectStorage
|
|
160
|
+
|
|
161
|
+
# Or generate both at once
|
|
162
|
+
npx nest-hex generate full ObjectStorage S3
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
**See [CLI Documentation](./docs/cli.md) for complete command reference, configuration options, and template customization.**
|
|
166
|
+
|
|
155
167
|
## Key Benefits
|
|
156
168
|
|
|
157
169
|
### Before (Manual Boilerplate)
|
|
158
170
|
|
|
159
171
|
```typescript
|
|
160
|
-
// Lots of manual wiring...
|
|
161
172
|
@Module({})
|
|
162
173
|
export class S3StorageModule {
|
|
163
174
|
static register(options: S3Options): DynamicModule {
|
|
@@ -169,7 +180,7 @@ export class S3StorageModule {
|
|
|
169
180
|
// More boilerplate...
|
|
170
181
|
],
|
|
171
182
|
exports: [STORAGE_PORT],
|
|
172
|
-
}
|
|
183
|
+
}
|
|
173
184
|
}
|
|
174
185
|
}
|
|
175
186
|
```
|
|
@@ -177,231 +188,109 @@ export class S3StorageModule {
|
|
|
177
188
|
### After (With nest-hex)
|
|
178
189
|
|
|
179
190
|
```typescript
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
implementation: S3StorageService,
|
|
191
|
+
@Adapter({
|
|
192
|
+
portToken: STORAGE_PORT,
|
|
193
|
+
implementation: S3StorageService
|
|
184
194
|
})
|
|
185
|
-
export class S3Adapter extends
|
|
195
|
+
export class S3Adapter extends AdapterBase<S3Options> {}
|
|
186
196
|
```
|
|
187
197
|
|
|
188
|
-
##
|
|
198
|
+
## Swappable Infrastructure
|
|
189
199
|
|
|
190
|
-
|
|
200
|
+
The real power: swap infrastructure without touching business logic.
|
|
191
201
|
|
|
192
202
|
```typescript
|
|
193
|
-
|
|
203
|
+
// Development: Use local filesystem
|
|
204
|
+
const adapter = process.env.NODE_ENV === 'production'
|
|
205
|
+
? S3Adapter.register({ bucket: 'prod-bucket', region: 'us-east-1' })
|
|
206
|
+
: LocalStorageAdapter.register({ basePath: './uploads' })
|
|
194
207
|
|
|
195
208
|
@Module({
|
|
196
|
-
imports: [
|
|
197
|
-
StorageModule.register({
|
|
198
|
-
adapter: S3Adapter.registerAsync({
|
|
199
|
-
imports: [ConfigModule],
|
|
200
|
-
inject: [ConfigService],
|
|
201
|
-
useFactory: (config: ConfigService) => ({
|
|
202
|
-
bucket: config.get('S3_BUCKET'),
|
|
203
|
-
region: config.get('AWS_REGION'),
|
|
204
|
-
accessKeyId: config.get('AWS_ACCESS_KEY_ID'),
|
|
205
|
-
secretAccessKey: config.get('AWS_SECRET_ACCESS_KEY'),
|
|
206
|
-
}),
|
|
207
|
-
}),
|
|
208
|
-
}),
|
|
209
|
-
],
|
|
209
|
+
imports: [FileModule.register({ adapter })]
|
|
210
210
|
})
|
|
211
211
|
export class AppModule {}
|
|
212
212
|
```
|
|
213
213
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
```typescript
|
|
217
|
-
@Port({
|
|
218
|
-
token: HTTP_CLIENT_PORT,
|
|
219
|
-
implementation: AxiosHttpClient,
|
|
220
|
-
})
|
|
221
|
-
class AxiosAdapterClass extends Adapter<AxiosOptions> {
|
|
222
|
-
protected override imports(options: AxiosOptions) {
|
|
223
|
-
return [
|
|
224
|
-
HttpModule.register({
|
|
225
|
-
baseURL: options.baseUrl,
|
|
226
|
-
timeout: options.timeout,
|
|
227
|
-
}),
|
|
228
|
-
];
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
protected override extraPoviders(options: AxiosOptions) {
|
|
232
|
-
return [
|
|
233
|
-
{
|
|
234
|
-
provide: 'HTTP_CLIENT_CONFIG',
|
|
235
|
-
useValue: options,
|
|
236
|
-
},
|
|
237
|
-
];
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
```
|
|
214
|
+
Your `FileService` business logic **never changes**. Only the adapter changes.
|
|
241
215
|
|
|
242
|
-
|
|
216
|
+
## Advanced Features
|
|
243
217
|
|
|
244
|
-
|
|
218
|
+
### Async Configuration with Dependency Injection
|
|
245
219
|
|
|
246
220
|
```typescript
|
|
247
|
-
// Development: Use filesystem storage
|
|
248
|
-
import FilesystemAdapter from './storage/adapters/filesystem.adapter';
|
|
249
|
-
|
|
250
|
-
// Production: Use AWS S3
|
|
251
|
-
import S3Adapter from './storage/adapters/s3.adapter';
|
|
252
|
-
|
|
253
|
-
const adapter = process.env.NODE_ENV === 'production'
|
|
254
|
-
? S3Adapter.register({ bucket: 'prod-bucket', region: 'us-east-1' })
|
|
255
|
-
: FilesystemAdapter.register({ basePath: './uploads' });
|
|
256
|
-
|
|
257
221
|
@Module({
|
|
258
222
|
imports: [
|
|
259
|
-
|
|
260
|
-
|
|
223
|
+
FileModule.register({
|
|
224
|
+
adapter: S3Adapter.registerAsync({
|
|
225
|
+
imports: [ConfigModule],
|
|
226
|
+
inject: [ConfigService],
|
|
227
|
+
useFactory: (config: ConfigService) => ({
|
|
228
|
+
bucket: config.get('S3_BUCKET')!,
|
|
229
|
+
region: config.get('AWS_REGION')!
|
|
230
|
+
})
|
|
231
|
+
})
|
|
232
|
+
})
|
|
233
|
+
]
|
|
261
234
|
})
|
|
262
235
|
export class AppModule {}
|
|
263
236
|
```
|
|
264
237
|
|
|
265
|
-
###
|
|
238
|
+
### Adapters with Dependencies
|
|
266
239
|
|
|
267
240
|
```typescript
|
|
268
|
-
|
|
241
|
+
@Adapter({
|
|
242
|
+
portToken: HTTP_CLIENT_PORT,
|
|
243
|
+
implementation: AxiosHttpClient,
|
|
244
|
+
imports: [HttpModule],
|
|
245
|
+
providers: [
|
|
246
|
+
{ provide: 'HTTP_CONFIG', useValue: { timeout: 5000 } }
|
|
247
|
+
]
|
|
248
|
+
})
|
|
249
|
+
export class AxiosAdapter extends AdapterBase<AxiosOptions> {}
|
|
250
|
+
```
|
|
269
251
|
|
|
270
|
-
|
|
271
|
-
async upload(file: Buffer, key: string) {
|
|
272
|
-
return { url: `mock://storage/${key}` };
|
|
273
|
-
}
|
|
252
|
+
### Mock Adapters for Testing
|
|
274
253
|
|
|
275
|
-
|
|
276
|
-
|
|
254
|
+
```typescript
|
|
255
|
+
@Injectable()
|
|
256
|
+
class MockStorageService implements StoragePort {
|
|
257
|
+
async upload(key: string, data: Buffer): Promise<string> {
|
|
258
|
+
return `mock://storage/${key}`
|
|
259
|
+
}
|
|
260
|
+
async download(key: string): Promise<Buffer> {
|
|
261
|
+
return Buffer.from('mock data')
|
|
277
262
|
}
|
|
278
263
|
}
|
|
279
264
|
|
|
280
|
-
@
|
|
281
|
-
|
|
282
|
-
implementation: MockStorageService
|
|
265
|
+
@Adapter({
|
|
266
|
+
portToken: STORAGE_PORT,
|
|
267
|
+
implementation: MockStorageService
|
|
283
268
|
})
|
|
284
|
-
export class MockStorageAdapter extends
|
|
269
|
+
export class MockStorageAdapter extends AdapterBase<{}> {}
|
|
285
270
|
|
|
286
271
|
// Use in tests
|
|
287
272
|
const module = await Test.createTestingModule({
|
|
288
273
|
imports: [
|
|
289
|
-
|
|
290
|
-
adapter: MockStorageAdapter.register(
|
|
291
|
-
})
|
|
292
|
-
]
|
|
293
|
-
}).compile()
|
|
294
|
-
```
|
|
295
|
-
|
|
296
|
-
## API Reference
|
|
297
|
-
|
|
298
|
-
### Core Classes
|
|
299
|
-
|
|
300
|
-
#### `Adapter<TOptions>`
|
|
301
|
-
|
|
302
|
-
Abstract base class for building adapter modules.
|
|
303
|
-
|
|
304
|
-
**Methods:**
|
|
305
|
-
- `static register<TToken, TOptions>(options: TOptions): AdapterModule<TToken>` - Synchronous registration
|
|
306
|
-
- `static registerAsync<TToken, TOptions>(config: AsyncConfig): AdapterModule<TToken>` - Async registration with DI
|
|
307
|
-
|
|
308
|
-
**Protected Hooks:**
|
|
309
|
-
- `protected imports(options?: TOptions): unknown[]` - Override to import other NestJS modules
|
|
310
|
-
- `protected extraPoviders(options: TOptions): Port[]` - Override to add additional providers
|
|
311
|
-
|
|
312
|
-
#### `PortModule<TService>`
|
|
313
|
-
|
|
314
|
-
Abstract base class for building port modules that consume adapters.
|
|
315
|
-
|
|
316
|
-
**Methods:**
|
|
317
|
-
- `static register<TToken>({ adapter }: { adapter?: AdapterModule<TToken> }): DynamicModule`
|
|
318
|
-
|
|
319
|
-
### Decorators
|
|
320
|
-
|
|
321
|
-
#### `@Port({ token, implementation })`
|
|
322
|
-
|
|
323
|
-
Class decorator that declares which port token an adapter provides and its implementation class.
|
|
324
|
-
|
|
325
|
-
**Parameters:**
|
|
326
|
-
- `token: TToken` - The port token (usually a Symbol)
|
|
327
|
-
- `implementation: Type<unknown>` - The concrete implementation class
|
|
328
|
-
|
|
329
|
-
**Example:**
|
|
330
|
-
```typescript
|
|
331
|
-
@Port({
|
|
332
|
-
token: STORAGE_PORT,
|
|
333
|
-
implementation: S3StorageService,
|
|
334
|
-
})
|
|
335
|
-
class S3Adapter extends Adapter<S3Options> {}
|
|
336
|
-
```
|
|
337
|
-
|
|
338
|
-
#### `@InjectPort(token)`
|
|
339
|
-
|
|
340
|
-
Parameter decorator for injecting a port token into service constructors.
|
|
341
|
-
|
|
342
|
-
**Example:**
|
|
343
|
-
```typescript
|
|
344
|
-
constructor(
|
|
345
|
-
@InjectPort(STORAGE_PORT)
|
|
346
|
-
private readonly storage: StoragePort,
|
|
347
|
-
) {}
|
|
348
|
-
```
|
|
349
|
-
|
|
350
|
-
### Types
|
|
351
|
-
|
|
352
|
-
#### `AdapterModule<TToken>`
|
|
353
|
-
|
|
354
|
-
A DynamicModule that carries compile-time proof it provides `TToken`.
|
|
355
|
-
|
|
356
|
-
```typescript
|
|
357
|
-
type AdapterModule<TToken> = DynamicModule & {
|
|
358
|
-
__provides: TToken;
|
|
359
|
-
};
|
|
274
|
+
FileModule.register({
|
|
275
|
+
adapter: MockStorageAdapter.register({})
|
|
276
|
+
})
|
|
277
|
+
]
|
|
278
|
+
}).compile()
|
|
360
279
|
```
|
|
361
280
|
|
|
362
|
-
##
|
|
363
|
-
|
|
364
|
-
### ✅ Do's
|
|
365
|
-
|
|
366
|
-
- **Export port tokens, not provider objects**
|
|
367
|
-
```typescript
|
|
368
|
-
exports: [STORAGE_PORT] // ✅ Correct
|
|
369
|
-
```
|
|
370
|
-
|
|
371
|
-
- **Keep configuration in the app layer**
|
|
372
|
-
```typescript
|
|
373
|
-
// ✅ Good: App provides config
|
|
374
|
-
S3Adapter.register({
|
|
375
|
-
bucket: process.env.S3_BUCKET,
|
|
376
|
-
})
|
|
377
|
-
```
|
|
378
|
-
|
|
379
|
-
- **Use `@InjectPort` for clarity**
|
|
380
|
-
```typescript
|
|
381
|
-
@InjectPort(STORAGE_PORT) // ✅ Clear intent
|
|
382
|
-
```
|
|
383
|
-
|
|
384
|
-
- **Create small, focused adapters**
|
|
385
|
-
- One adapter = one infrastructure concern
|
|
386
|
-
|
|
387
|
-
### ❌ Don'ts
|
|
388
|
-
|
|
389
|
-
- **Don't export provider objects**
|
|
390
|
-
```typescript
|
|
391
|
-
exports: [{ provide: STORAGE_PORT, useExisting: S3Service }] // ❌ Wrong
|
|
392
|
-
```
|
|
281
|
+
## Documentation
|
|
393
282
|
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
class S3Adapter {
|
|
398
|
-
bucket = process.env.S3_BUCKET;
|
|
399
|
-
}
|
|
400
|
-
```
|
|
283
|
+
📚 **Complete Documentation:**
|
|
284
|
+
- **[Library Documentation](./docs/library.md)** - Full API reference, architecture guide, advanced patterns, and examples
|
|
285
|
+
- **[CLI Documentation](./docs/cli.md)** - Complete CLI reference, configuration, templates, and best practices
|
|
401
286
|
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
287
|
+
📖 **Quick Links:**
|
|
288
|
+
- [Core Concepts](./docs/library.md#core-concepts) - Understand ports, adapters, and services
|
|
289
|
+
- [Why Hexagonal Architecture?](./docs/library.md#why-hexagonal-architecture) - Benefits with code examples
|
|
290
|
+
- [Architecture Overview](./docs/library.md#architecture-overview) - Visual diagrams
|
|
291
|
+
- [API Reference](./docs/library.md#api-reference) - Complete API documentation
|
|
292
|
+
- [Testing Guide](./docs/library.md#testing) - Mock adapters and integration testing
|
|
293
|
+
- [Migration Guide](./docs/library.md#migration-guide) - Upgrading from @Port to @Adapter
|
|
405
294
|
|
|
406
295
|
## Examples
|
|
407
296
|
|
|
@@ -409,17 +298,16 @@ See the [`examples/`](./examples) directory for complete working examples:
|
|
|
409
298
|
|
|
410
299
|
- **Object Storage** - S3 adapter with file upload/download
|
|
411
300
|
- **Currency Rates** - HTTP API adapter with rate conversion
|
|
412
|
-
- **
|
|
413
|
-
|
|
414
|
-
## Documentation
|
|
415
|
-
|
|
416
|
-
- 📖 [Full Specification](./spec/spec.md) - Complete implementation guide with AWS S3 and HTTP API examples
|
|
417
|
-
- 🔧 [API Reference](#api-reference) - Detailed API documentation
|
|
301
|
+
- **Mock Patterns** - Testing with mock adapters
|
|
418
302
|
|
|
419
303
|
## License
|
|
420
304
|
|
|
421
|
-
MIT
|
|
305
|
+
MIT © [Your Name]
|
|
422
306
|
|
|
423
307
|
## Contributing
|
|
424
308
|
|
|
425
|
-
Contributions are welcome! Please
|
|
309
|
+
Contributions are welcome! Please read our [Contributing Guide](./CONTRIBUTING.md) for details on our code of conduct and development process.
|
|
310
|
+
|
|
311
|
+
---
|
|
312
|
+
|
|
313
|
+
**Built with ❤️ for the NestJS community**
|