nest-hex 0.2.2 โ†’ 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 CHANGED
@@ -1,172 +1,28 @@
1
1
  # nest-hex
2
2
 
3
- > A tiny, **class-based**, **NestJS-native** helper library for building **pluggable adapters** following the Ports & Adapters (Hexagonal Architecture) pattern with minimal boilerplate and great developer experience.
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
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
6
 
7
- ## Why Hexagonal Architecture?
7
+ ## What is nest-hex?
8
8
 
9
- **Hexagonal Architecture (Ports & Adapters)** is a powerful pattern for building maintainable, testable, and adaptable applications. Here's why it matters:
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
- ### ๐ŸŽฏ Keep Domain Logic Pure
12
- Your business logic should never depend on infrastructure details like databases, APIs, or file systems. By defining **ports** (interfaces), your domain layer stays clean and focused on what matters: solving business problems.
11
+ ### Why Hexagonal Architecture?
13
12
 
14
- ### ๐Ÿ”Œ Pluggable Infrastructure
15
- Need to switch from AWS S3 to Google Cloud Storage? Replace MongoDB with PostgreSQL? Change from REST to GraphQL? With hexagonal architecture, you just swap the **adapter** โ€“ your domain logic never changes.
16
-
17
- ### ๐Ÿงช Effortless Testing
18
- Mock external services by creating test adapters. No complex setup, no database connections, no API calls. Just simple, fast unit tests that focus on business logic.
19
-
20
- ### ๐ŸŒ Environment Flexibility
21
- - **Development**: Use filesystem storage
22
- - **Testing**: Use in-memory mocks
23
- - **Production**: Use AWS S3
24
-
25
- Same domain code, different adapters. Configure once, swap anywhere.
26
-
27
- ### ๐Ÿ“ฆ Independent Deployment
28
- Infrastructure changes don't require redeploying your entire application. Update an adapter independently without touching core business logic.
29
-
30
- ## Why This Library?
31
-
32
- Building NestJS applications with the Ports & Adapters pattern involves repetitive boilerplate:
33
-
34
- - Registering concrete implementation classes
35
- - Aliasing port tokens to implementations (`useExisting`)
36
- - Exporting only the port token (not provider objects)
37
- - Supporting both `register()` and `registerAsync()` patterns
38
- - Keeping the app responsible for configuration (no `process.env` in libraries)
39
-
40
- **nest-hex eliminates this boilerplate** while maintaining compile-time type safety and providing a delightful developer experience through both decorators and a powerful CLI.
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
41
17
 
42
18
  ## Features
43
19
 
44
- - ๐ŸŽฏ **Declarative**: Declare port tokens and implementations once using `@Port({ token, implementation })`
45
- - ๐Ÿ—๏ธ **Class-based**: Use standard NestJS dynamic modules, no function factories required
46
- - ๐Ÿ”’ **Type-safe**: `AdapterModule<TToken>` carries compile-time proof of which token it provides
47
- - โšก **Zero runtime overhead**: Uses TypeScript decorators and metadata, minimal abstraction
48
- - ๐Ÿ“ฆ **Tiny**: Core library is under 1KB minified
49
- - ๐Ÿงช **Testable**: Easily mock adapters for testing
50
- - ๐Ÿ› ๏ธ **Powerful CLI**: Generate ports, adapters, and services with a single command
51
-
52
- ## CLI
53
-
54
- **nest-hex** includes a powerful CLI to scaffold ports, adapters, and services instantly. No more manual file creation!
55
-
56
- ### Quick Start
57
-
58
- ```bash
59
- # Initialize configuration
60
- npx nest-hex init
61
-
62
- # Generate a port (domain interface)
63
- npx nest-hex generate port ObjectStorage
64
-
65
- # Generate an adapter for the port
66
- npx nest-hex generate adapter S3 --port ObjectStorage
67
-
68
- # Generate both port and adapter together
69
- npx nest-hex generate full ObjectStorage S3
70
-
71
- # Generate a service that uses a port
72
- npx nest-hex generate service FileUpload
73
- ```
74
-
75
- ### Available Commands
76
-
77
- #### `init`
78
- Create a `nest-hex.config.ts` configuration file in your project.
79
-
80
- ```bash
81
- npx nest-hex init
82
- ```
83
-
84
- #### `generate` (or `g`)
85
- Generate ports, adapters, services, or complete modules.
86
-
87
- ```bash
88
- # Generate a port
89
- npx nest-hex generate port <name>
90
- npx nest-hex g port PaymentGateway
91
-
92
- # Generate an adapter
93
- npx nest-hex generate adapter <name> --port <portName>
94
- npx nest-hex g adapter Stripe --port PaymentGateway
95
-
96
- # Generate both port and adapter
97
- npx nest-hex generate full <portName> <adapterName>
98
- npx nest-hex g full EmailService SendGrid
99
-
100
- # Generate a service
101
- npx nest-hex generate service <name>
102
- npx nest-hex g service UserRegistration
103
- ```
104
-
105
- ### Interactive Mode
106
-
107
- Run commands without arguments for interactive prompts:
108
-
109
- ```bash
110
- npx nest-hex generate
111
- # โ†’ Select type: port, adapter, service, or full
112
- # โ†’ Enter name(s)
113
- # โ†’ Files generated!
114
- ```
115
-
116
- ### Configuration
117
-
118
- The CLI uses `nest-hex.config.ts` to customize output paths and naming conventions:
119
-
120
- ```typescript
121
- // nest-hex.config.ts
122
- import { defineConfig } from 'nest-hex/cli';
123
-
124
- export default defineConfig({
125
- output: {
126
- portsDir: 'src/domain/ports', // Where to generate ports
127
- adaptersDir: 'src/infrastructure', // Where to generate adapters
128
- servicesDir: 'src/application', // Where to generate services
129
- },
130
- naming: {
131
- portSuffix: 'Port', // ObjectStoragePort
132
- tokenSuffix: '_PORT', // OBJECT_STORAGE_PORT
133
- adapterSuffix: 'Adapter', // S3Adapter
134
- serviceSuffix: 'Service', // FileUploadService
135
- },
136
- });
137
- ```
138
-
139
- ### What Gets Generated
140
-
141
- #### Port Generation
142
- Creates a complete port with:
143
- - Token definition (`OBJECT_STORAGE_PORT`)
144
- - TypeScript interface with example methods
145
- - Service implementation with `@InjectPort`
146
- - Module that accepts adapters
147
- - Barrel exports (`index.ts`)
148
-
149
- #### Adapter Generation
150
- Creates a production-ready adapter with:
151
- - Implementation service class
152
- - Options interface
153
- - Adapter class with `@Port` decorator
154
- - Complete TypeScript types
155
- - Barrel exports
156
-
157
- #### Service Generation
158
- Creates a domain service with:
159
- - Service class with `@InjectPort` usage
160
- - Type-safe port injection
161
- - Example business logic methods
162
-
163
- ### CLI Benefits
164
-
165
- โœ… **Instant scaffolding** - Generate complete, type-safe modules in seconds
166
- โœ… **Consistent structure** - All team members follow the same patterns
167
- โœ… **Best practices built-in** - Generated code follows hexagonal architecture principles
168
- โœ… **Customizable** - Configure paths and naming to match your project
169
- โœ… **Interactive** - Friendly prompts guide you through generation
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
170
26
 
171
27
  ## Installation
172
28
 
@@ -180,8 +36,7 @@ pnpm add nest-hex
180
36
  bun add nest-hex
181
37
  ```
182
38
 
183
- ### Peer Dependencies
184
-
39
+ **Peer dependencies:**
185
40
  ```bash
186
41
  npm install @nestjs/common @nestjs/core reflect-metadata
187
42
  ```
@@ -192,11 +47,11 @@ npm install @nestjs/common @nestjs/core reflect-metadata
192
47
 
193
48
  ```typescript
194
49
  // storage.port.ts
195
- export const STORAGE_PORT = Symbol('STORAGE_PORT');
50
+ export const STORAGE_PORT = Symbol('STORAGE_PORT')
196
51
 
197
52
  export interface StoragePort {
198
- upload(file: Buffer, key: string): Promise<{ url: string }>;
199
- download(key: string): Promise<Buffer>;
53
+ upload(key: string, data: Buffer): Promise<string>
54
+ download(key: string): Promise<Buffer>
200
55
  }
201
56
  ```
202
57
 
@@ -204,103 +59,116 @@ export interface StoragePort {
204
59
 
205
60
  ```typescript
206
61
  // s3.adapter.ts
207
- import { Injectable } from '@nestjs/common';
208
- import { Adapter, Port } from 'nest-hex';
209
- 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'
210
65
 
211
66
  // Implementation service
212
67
  @Injectable()
213
- class S3StorageService implements StoragePort {
214
- async upload(file: Buffer, key: string) {
68
+ class S3Service implements StoragePort {
69
+ constructor(private options: { bucket: string; region: string }) {}
70
+
71
+ async upload(key: string, data: Buffer): Promise<string> {
215
72
  // AWS S3 upload logic here
216
- return { url: `https://s3.amazonaws.com/bucket/${key}` };
73
+ return `https://s3.amazonaws.com/${this.options.bucket}/${key}`
217
74
  }
218
75
 
219
- async download(key: string) {
76
+ async download(key: string): Promise<Buffer> {
220
77
  // AWS S3 download logic here
221
- return Buffer.from('file contents');
78
+ return Buffer.from('file contents')
222
79
  }
223
80
  }
224
81
 
225
- // Adapter configuration
226
- interface S3Options {
227
- bucket: string;
228
- region: string;
229
- accessKeyId?: string;
230
- secretAccessKey?: string;
231
- }
232
-
233
82
  // Adapter module - single decorator declares everything!
234
- @Port({
235
- token: STORAGE_PORT,
236
- implementation: S3StorageService,
83
+ @Adapter({
84
+ portToken: STORAGE_PORT,
85
+ implementation: S3Service
237
86
  })
238
- export class S3Adapter extends Adapter<S3Options> {}
87
+ export class S3Adapter extends AdapterBase<{ bucket: string; region: string }> {}
239
88
  ```
240
89
 
241
- ### 3. Create a Port Module (Domain Service)
90
+ ### 3. Create a Domain Service
242
91
 
243
92
  ```typescript
244
- // storage.module.ts
245
- import { Injectable, Module } from '@nestjs/common';
246
- import { InjectPort, PortModule } from 'nest-hex';
247
- 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'
248
97
 
249
- // Domain service that uses the port
250
98
  @Injectable()
251
- export class StorageService {
99
+ export class FileService {
252
100
  constructor(
253
101
  @InjectPort(STORAGE_PORT)
254
- private readonly storage: StoragePort,
102
+ private readonly storage: StoragePort
255
103
  ) {}
256
104
 
257
- async uploadUserAvatar(userId: string, image: Buffer) {
258
- const key = `avatars/${userId}.jpg`;
259
- return this.storage.upload(image, key);
260
- }
261
-
262
- async downloadUserAvatar(userId: string) {
263
- const key = `avatars/${userId}.jpg`;
264
- 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)
265
108
  }
266
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'
267
119
 
268
- // Port module that accepts any adapter
269
120
  @Module({})
270
- export class StorageModule extends PortModule<typeof StorageService> {}
121
+ export class FileModule extends PortModule<typeof FileService> {}
271
122
  ```
272
123
 
273
- ### 4. Wire It Up in Your App
124
+ ### 5. Wire It Up
274
125
 
275
126
  ```typescript
276
127
  // app.module.ts
277
- import { Module } from '@nestjs/common';
278
- import { StorageModule } from './storage/storage.module';
279
- import S3Adapter from './storage/adapters/s3.adapter';
128
+ import { Module } from '@nestjs/common'
129
+ import { FileModule } from './file.module'
130
+ import { S3Adapter } from './s3.adapter'
280
131
 
281
132
  @Module({
282
133
  imports: [
283
- StorageModule.register({
134
+ FileModule.register({
284
135
  adapter: S3Adapter.register({
285
- bucket: 'my-app-uploads',
286
- region: 'us-east-1',
287
- accessKeyId: process.env.AWS_ACCESS_KEY_ID,
288
- secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
289
- }),
290
- }),
291
- ],
136
+ bucket: process.env.S3_BUCKET || 'my-bucket',
137
+ region: process.env.AWS_REGION || 'us-east-1'
138
+ })
139
+ })
140
+ ]
292
141
  })
293
142
  export class AppModule {}
294
143
  ```
295
144
 
296
145
  That's it! You now have a fully type-safe, pluggable storage adapter. ๐ŸŽ‰
297
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
+
298
167
  ## Key Benefits
299
168
 
300
169
  ### Before (Manual Boilerplate)
301
170
 
302
171
  ```typescript
303
- // Lots of manual wiring...
304
172
  @Module({})
305
173
  export class S3StorageModule {
306
174
  static register(options: S3Options): DynamicModule {
@@ -312,7 +180,7 @@ export class S3StorageModule {
312
180
  // More boilerplate...
313
181
  ],
314
182
  exports: [STORAGE_PORT],
315
- };
183
+ }
316
184
  }
317
185
  }
318
186
  ```
@@ -320,267 +188,109 @@ export class S3StorageModule {
320
188
  ### After (With nest-hex)
321
189
 
322
190
  ```typescript
323
- // Clean and declarative!
324
- @Port({
325
- token: STORAGE_PORT,
326
- implementation: S3StorageService,
191
+ @Adapter({
192
+ portToken: STORAGE_PORT,
193
+ implementation: S3StorageService
327
194
  })
328
- export class S3Adapter extends Adapter<S3Options> {}
195
+ export class S3Adapter extends AdapterBase<S3Options> {}
329
196
  ```
330
197
 
331
- ## Advanced Usage
198
+ ## Swappable Infrastructure
332
199
 
333
- ### Async Registration with Dependency Injection
200
+ The real power: swap infrastructure without touching business logic.
334
201
 
335
202
  ```typescript
336
- import { ConfigModule, ConfigService } from '@nestjs/config';
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' })
337
207
 
338
208
  @Module({
339
- imports: [
340
- StorageModule.register({
341
- adapter: S3Adapter.registerAsync({
342
- imports: [ConfigModule],
343
- inject: [ConfigService],
344
- useFactory: (config: ConfigService) => ({
345
- bucket: config.get('S3_BUCKET'),
346
- region: config.get('AWS_REGION'),
347
- accessKeyId: config.get('AWS_ACCESS_KEY_ID'),
348
- secretAccessKey: config.get('AWS_SECRET_ACCESS_KEY'),
349
- }),
350
- }),
351
- }),
352
- ],
209
+ imports: [FileModule.register({ adapter })]
353
210
  })
354
211
  export class AppModule {}
355
212
  ```
356
213
 
357
- ### Custom Imports and Extra Ports
358
-
359
- ```typescript
360
- @Port({
361
- token: HTTP_CLIENT_PORT,
362
- implementation: AxiosHttpClient,
363
- })
364
- class AxiosAdapterClass extends Adapter<AxiosOptions> {
365
- protected override imports(options: AxiosOptions) {
366
- return [
367
- HttpModule.register({
368
- baseURL: options.baseUrl,
369
- timeout: options.timeout,
370
- }),
371
- ];
372
- }
373
-
374
- protected override extraPoviders(options: AxiosOptions) {
375
- return [
376
- {
377
- provide: 'HTTP_CLIENT_CONFIG',
378
- useValue: options,
379
- },
380
- ];
381
- }
382
- }
383
- ```
384
-
385
- ### Swapping Adapters - The Power of Pluggability
214
+ Your `FileService` business logic **never changes**. Only the adapter changes.
386
215
 
387
- **This is the core benefit of hexagonal architecture**: swap infrastructure without touching business logic.
216
+ ## Advanced Features
388
217
 
389
- #### Environment-Based Swapping
218
+ ### Async Configuration with Dependency Injection
390
219
 
391
220
  ```typescript
392
- // Development: Use filesystem storage
393
- import FilesystemAdapter from './storage/adapters/filesystem.adapter';
394
-
395
- // Production: Use AWS S3
396
- import S3Adapter from './storage/adapters/s3.adapter';
397
-
398
- const adapter = process.env.NODE_ENV === 'production'
399
- ? S3Adapter.register({ bucket: 'prod-bucket', region: 'us-east-1' })
400
- : FilesystemAdapter.register({ basePath: './uploads' });
401
-
402
221
  @Module({
403
222
  imports: [
404
- StorageModule.register({ adapter }),
405
- ],
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
+ ]
406
234
  })
407
235
  export class AppModule {}
408
236
  ```
409
237
 
410
- #### Multi-Cloud Strategy
411
-
412
- ```typescript
413
- // Easily switch cloud providers without changing domain code
414
- const storageAdapter = process.env.CLOUD_PROVIDER === 'aws'
415
- ? S3Adapter.register({ bucket: 'my-bucket', region: 'us-east-1' })
416
- : process.env.CLOUD_PROVIDER === 'gcp'
417
- ? GCSAdapter.register({ bucket: 'my-bucket' })
418
- : AzureBlobAdapter.register({ containerName: 'my-container' });
419
- ```
420
-
421
- #### Feature Flags
422
-
423
- ```typescript
424
- // Gradually migrate to new infrastructure
425
- const emailAdapter = featureFlags.useNewEmailProvider
426
- ? SendGridAdapter.register({ apiKey: process.env.SENDGRID_KEY })
427
- : SESAdapter.register({ region: 'us-east-1' });
428
- ```
429
-
430
- #### Testing with Mocks
238
+ ### Adapters with Dependencies
431
239
 
432
240
  ```typescript
433
- // Test module: in-memory mock
434
- const testAdapter = MockStorageAdapter.register();
435
-
436
- // Production module: real infrastructure
437
- const prodAdapter = S3Adapter.register({ bucket: 'prod' });
438
-
439
- // Same domain code, different runtime behavior
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> {}
440
250
  ```
441
251
 
442
- **Key Point**: Your `StorageService` business logic **never changes**. Only the infrastructure adapter changes. This is the essence of maintainable architecture.
443
-
444
- ### Testing with Mock Adapters
252
+ ### Mock Adapters for Testing
445
253
 
446
254
  ```typescript
447
- import { Adapter, Port } from 'nest-hex';
448
-
449
- class MockStorageService {
450
- async upload(file: Buffer, key: string) {
451
- return { url: `mock://storage/${key}` };
255
+ @Injectable()
256
+ class MockStorageService implements StoragePort {
257
+ async upload(key: string, data: Buffer): Promise<string> {
258
+ return `mock://storage/${key}`
452
259
  }
453
-
454
- async download(key: string) {
455
- return Buffer.from('mock file contents');
260
+ async download(key: string): Promise<Buffer> {
261
+ return Buffer.from('mock data')
456
262
  }
457
263
  }
458
264
 
459
- @Port({
460
- token: STORAGE_PORT,
461
- implementation: MockStorageService,
265
+ @Adapter({
266
+ portToken: STORAGE_PORT,
267
+ implementation: MockStorageService
462
268
  })
463
- export class MockStorageAdapter extends Adapter<void> {}
269
+ export class MockStorageAdapter extends AdapterBase<{}> {}
464
270
 
465
271
  // Use in tests
466
272
  const module = await Test.createTestingModule({
467
273
  imports: [
468
- StorageModule.register({
469
- adapter: MockStorageAdapter.register(undefined),
470
- }),
471
- ],
472
- }).compile();
473
- ```
474
-
475
- ## API Reference
476
-
477
- ### Core Classes
478
-
479
- #### `Adapter<TOptions>`
480
-
481
- Abstract base class for building adapter modules.
482
-
483
- **Methods:**
484
- - `static register<TToken, TOptions>(options: TOptions): AdapterModule<TToken>` - Synchronous registration
485
- - `static registerAsync<TToken, TOptions>(config: AsyncConfig): AdapterModule<TToken>` - Async registration with DI
486
-
487
- **Protected Hooks:**
488
- - `protected imports(options?: TOptions): unknown[]` - Override to import other NestJS modules
489
- - `protected extraPoviders(options: TOptions): Port[]` - Override to add additional providers
490
-
491
- #### `PortModule<TService>`
492
-
493
- Abstract base class for building port modules that consume adapters.
494
-
495
- **Methods:**
496
- - `static register<TToken>({ adapter }: { adapter?: AdapterModule<TToken> }): DynamicModule`
497
-
498
- ### Decorators
499
-
500
- #### `@Port({ token, implementation })`
501
-
502
- Class decorator that declares which port token an adapter provides and its implementation class.
503
-
504
- **Parameters:**
505
- - `token: TToken` - The port token (usually a Symbol)
506
- - `implementation: Type<unknown>` - The concrete implementation class
507
-
508
- **Example:**
509
- ```typescript
510
- @Port({
511
- token: STORAGE_PORT,
512
- implementation: S3StorageService,
513
- })
514
- class S3Adapter extends Adapter<S3Options> {}
515
- ```
516
-
517
- #### `@InjectPort(token)`
518
-
519
- Parameter decorator for injecting a port token into service constructors.
520
-
521
- **Example:**
522
- ```typescript
523
- constructor(
524
- @InjectPort(STORAGE_PORT)
525
- private readonly storage: StoragePort,
526
- ) {}
274
+ FileModule.register({
275
+ adapter: MockStorageAdapter.register({})
276
+ })
277
+ ]
278
+ }).compile()
527
279
  ```
528
280
 
529
- ### Types
530
-
531
- #### `AdapterModule<TToken>`
532
-
533
- A DynamicModule that carries compile-time proof it provides `TToken`.
534
-
535
- ```typescript
536
- type AdapterModule<TToken> = DynamicModule & {
537
- __provides: TToken;
538
- };
539
- ```
540
-
541
- ## Best Practices
542
-
543
- ### โœ… Do's
544
-
545
- - **Export port tokens, not provider objects**
546
- ```typescript
547
- exports: [STORAGE_PORT] // โœ… Correct
548
- ```
549
-
550
- - **Keep configuration in the app layer**
551
- ```typescript
552
- // โœ… Good: App provides config
553
- S3Adapter.register({
554
- bucket: process.env.S3_BUCKET,
555
- })
556
- ```
557
-
558
- - **Use `@InjectPort` for clarity**
559
- ```typescript
560
- @InjectPort(STORAGE_PORT) // โœ… Clear intent
561
- ```
562
-
563
- - **Create small, focused adapters**
564
- - One adapter = one infrastructure concern
565
-
566
- ### โŒ Don'ts
567
-
568
- - **Don't export provider objects**
569
- ```typescript
570
- exports: [{ provide: STORAGE_PORT, useExisting: S3Service }] // โŒ Wrong
571
- ```
281
+ ## Documentation
572
282
 
573
- - **Don't use `process.env` in adapters**
574
- ```typescript
575
- // โŒ Bad: Config hard-coded in adapter
576
- class S3Adapter {
577
- bucket = process.env.S3_BUCKET;
578
- }
579
- ```
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
580
286
 
581
- - **Don't mix domain logic with adapters**
582
- - Adapters = infrastructure only
583
- - Domain logic = port modules/services
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
584
294
 
585
295
  ## Examples
586
296
 
@@ -588,17 +298,16 @@ See the [`examples/`](./examples) directory for complete working examples:
588
298
 
589
299
  - **Object Storage** - S3 adapter with file upload/download
590
300
  - **Currency Rates** - HTTP API adapter with rate conversion
591
- - **Basic Examples** - Decorator usage patterns
592
-
593
- ## Documentation
594
-
595
- - ๐Ÿ“– [Full Specification](./spec/spec.md) - Complete implementation guide with AWS S3 and HTTP API examples
596
- - ๐Ÿ”ง [API Reference](#api-reference) - Detailed API documentation
301
+ - **Mock Patterns** - Testing with mock adapters
597
302
 
598
303
  ## License
599
304
 
600
- MIT
305
+ MIT ยฉ [Your Name]
601
306
 
602
307
  ## Contributing
603
308
 
604
- Contributions are welcome! Please see [CONTRIBUTING.md](./CONTRIBUTING.md) for contribution guidelines.
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**
@@ -1156,7 +1156,7 @@ __export(exports_base_generator, {
1156
1156
  });
1157
1157
  module.exports = __toCommonJS(exports_base_generator);
1158
1158
  var import_node_path3 = require("node:path");
1159
- var __dirname = "C:\\Users\\liorv\\OneDrive\\Desktop\\Projects\\Personal\\nestjs-adapter\\src\\cli\\generators";
1159
+ var __dirname = "/home/runner/work/nest-hex/nest-hex/src/cli/generators";
1160
1160
 
1161
1161
  class BaseGenerator {
1162
1162
  config;
@@ -1483,7 +1483,11 @@ function Confirm({
1483
1483
  // src/cli/ui/components/FileProgress.tsx
1484
1484
  var import_ink3 = require("ink");
1485
1485
  var jsx_dev_runtime3 = __toESM(require_jsx_dev_runtime(), 1);
1486
- function FileProgress({ fileName, status, error }) {
1486
+ function FileProgress({
1487
+ fileName,
1488
+ status,
1489
+ error
1490
+ }) {
1487
1491
  const getStatusIcon = () => {
1488
1492
  switch (status) {
1489
1493
  case "completed":
@@ -1558,7 +1562,11 @@ function FileProgress({ fileName, status, error }) {
1558
1562
  var import_ui3 = require("@inkjs/ui");
1559
1563
  var import_ink4 = require("ink");
1560
1564
  var jsx_dev_runtime4 = __toESM(require_jsx_dev_runtime(), 1);
1561
- function NameInput({ type, step, onSubmit }) {
1565
+ function NameInput({
1566
+ type,
1567
+ step,
1568
+ onSubmit
1569
+ }) {
1562
1570
  if (type === "full") {
1563
1571
  const currentStep = step || "port";
1564
1572
  const labels = {
@@ -1649,7 +1657,10 @@ function NameInput({ type, step, onSubmit }) {
1649
1657
  var import_ui4 = require("@inkjs/ui");
1650
1658
  var import_ink5 = require("ink");
1651
1659
  var jsx_dev_runtime5 = __toESM(require_jsx_dev_runtime(), 1);
1652
- function PortSelector({ ports, onSubmit }) {
1660
+ function PortSelector({
1661
+ ports,
1662
+ onSubmit
1663
+ }) {
1653
1664
  if (ports.length === 0) {
1654
1665
  return /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(import_ink5.Box, {
1655
1666
  flexDirection: "column",
@@ -1734,7 +1745,10 @@ function PortSelector({ ports, onSubmit }) {
1734
1745
  var import_ui5 = require("@inkjs/ui");
1735
1746
  var import_ink6 = require("ink");
1736
1747
  var jsx_dev_runtime6 = __toESM(require_jsx_dev_runtime(), 1);
1737
- function ProgressIndicator({ steps, title }) {
1748
+ function ProgressIndicator({
1749
+ steps,
1750
+ title
1751
+ }) {
1738
1752
  return /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(import_ink6.Box, {
1739
1753
  flexDirection: "column",
1740
1754
  paddingY: 1,
@@ -1155,7 +1155,7 @@ __export(exports_base_generator, {
1155
1155
  });
1156
1156
  module.exports = __toCommonJS(exports_base_generator);
1157
1157
  var import_node_path3 = require("node:path");
1158
- var __dirname = "C:\\Users\\liorv\\OneDrive\\Desktop\\Projects\\Personal\\nestjs-adapter\\src\\cli\\generators";
1158
+ var __dirname = "/home/runner/work/nest-hex/nest-hex/src/cli/generators";
1159
1159
 
1160
1160
  class BaseGenerator {
1161
1161
  config;
@@ -1482,7 +1482,11 @@ function Confirm({
1482
1482
  // src/cli/ui/components/FileProgress.tsx
1483
1483
  var import_ink3 = require("ink");
1484
1484
  var jsx_dev_runtime3 = __toESM(require_jsx_dev_runtime(), 1);
1485
- function FileProgress({ fileName, status, error }) {
1485
+ function FileProgress({
1486
+ fileName,
1487
+ status,
1488
+ error
1489
+ }) {
1486
1490
  const getStatusIcon = () => {
1487
1491
  switch (status) {
1488
1492
  case "completed":
@@ -1557,7 +1561,11 @@ function FileProgress({ fileName, status, error }) {
1557
1561
  var import_ui3 = require("@inkjs/ui");
1558
1562
  var import_ink4 = require("ink");
1559
1563
  var jsx_dev_runtime4 = __toESM(require_jsx_dev_runtime(), 1);
1560
- function NameInput({ type, step, onSubmit }) {
1564
+ function NameInput({
1565
+ type,
1566
+ step,
1567
+ onSubmit
1568
+ }) {
1561
1569
  if (type === "full") {
1562
1570
  const currentStep = step || "port";
1563
1571
  const labels = {
@@ -1648,7 +1656,10 @@ function NameInput({ type, step, onSubmit }) {
1648
1656
  var import_ui4 = require("@inkjs/ui");
1649
1657
  var import_ink5 = require("ink");
1650
1658
  var jsx_dev_runtime5 = __toESM(require_jsx_dev_runtime(), 1);
1651
- function PortSelector({ ports, onSubmit }) {
1659
+ function PortSelector({
1660
+ ports,
1661
+ onSubmit
1662
+ }) {
1652
1663
  if (ports.length === 0) {
1653
1664
  return /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(import_ink5.Box, {
1654
1665
  flexDirection: "column",
@@ -1733,7 +1744,10 @@ function PortSelector({ ports, onSubmit }) {
1733
1744
  var import_ui5 = require("@inkjs/ui");
1734
1745
  var import_ink6 = require("ink");
1735
1746
  var jsx_dev_runtime6 = __toESM(require_jsx_dev_runtime(), 1);
1736
- function ProgressIndicator({ steps, title }) {
1747
+ function ProgressIndicator({
1748
+ steps,
1749
+ title
1750
+ }) {
1737
1751
  return /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(import_ink6.Box, {
1738
1752
  flexDirection: "column",
1739
1753
  paddingY: 1,
@@ -237,7 +237,7 @@ __export(exports_base_generator, {
237
237
  });
238
238
  module.exports = __toCommonJS(exports_base_generator);
239
239
  var import_node_path2 = require("node:path");
240
- var __dirname = "C:\\Users\\liorv\\OneDrive\\Desktop\\Projects\\Personal\\nestjs-adapter\\src\\cli\\generators";
240
+ var __dirname = "/home/runner/work/nest-hex/nest-hex/src/cli/generators";
241
241
 
242
242
  class BaseGenerator {
243
243
  config;
@@ -237,7 +237,7 @@ __export(exports_base_generator, {
237
237
  });
238
238
  module.exports = __toCommonJS(exports_base_generator);
239
239
  var import_node_path2 = require("node:path");
240
- var __dirname = "C:\\Users\\liorv\\OneDrive\\Desktop\\Projects\\Personal\\nestjs-adapter\\src\\cli\\generators";
240
+ var __dirname = "/home/runner/work/nest-hex/nest-hex/src/cli/generators";
241
241
 
242
242
  class BaseGenerator {
243
243
  config;
@@ -193,6 +193,22 @@ declare abstract class BaseGenerator {
193
193
  protected generateFiles(files: FileToGenerate[], dryRun?: boolean): Promise<string[]>;
194
194
  }
195
195
  /**
196
+ * Generator for creating port files.
197
+ *
198
+ * Generates:
199
+ * - Port interface (domain contract)
200
+ * - Port token (dependency injection token)
201
+ * - Port service (optional - domain service using the port)
202
+ * - Port module (optional - feature module wrapper)
203
+ * - Index file (barrel exports)
204
+ */
205
+ declare class PortGenerator extends BaseGenerator {
206
+ /**
207
+ * Generate all port files.
208
+ */
209
+ generate(options: GeneratorOptions): Promise<GeneratorResult>;
210
+ }
211
+ /**
196
212
  * Additional options for adapter generation.
197
213
  */
198
214
  interface AdapterGeneratorOptions extends GeneratorOptions {
@@ -233,22 +249,6 @@ declare class AdapterGenerator extends BaseGenerator {
233
249
  generate(options: AdapterGeneratorOptions): Promise<GeneratorResult>;
234
250
  }
235
251
  /**
236
- * Generator for creating port files.
237
- *
238
- * Generates:
239
- * - Port interface (domain contract)
240
- * - Port token (dependency injection token)
241
- * - Port service (optional - domain service using the port)
242
- * - Port module (optional - feature module wrapper)
243
- * - Index file (barrel exports)
244
- */
245
- declare class PortGenerator extends BaseGenerator {
246
- /**
247
- * Generate all port files.
248
- */
249
- generate(options: GeneratorOptions): Promise<GeneratorResult>;
250
- }
251
- /**
252
252
  * Generator for creating standalone services.
253
253
  *
254
254
  * Generates:
@@ -237,7 +237,7 @@ __export(exports_base_generator, {
237
237
  });
238
238
  module.exports = __toCommonJS(exports_base_generator);
239
239
  var import_node_path2 = require("node:path");
240
- var __dirname = "C:\\Users\\liorv\\OneDrive\\Desktop\\Projects\\Personal\\nestjs-adapter\\src\\cli\\generators";
240
+ var __dirname = "/home/runner/work/nest-hex/nest-hex/src/cli/generators";
241
241
 
242
242
  class BaseGenerator {
243
243
  config;
@@ -237,7 +237,7 @@ __export(exports_base_generator, {
237
237
  });
238
238
  module.exports = __toCommonJS(exports_base_generator);
239
239
  var import_node_path2 = require("node:path");
240
- var __dirname = "C:\\Users\\liorv\\OneDrive\\Desktop\\Projects\\Personal\\nestjs-adapter\\src\\cli\\generators";
240
+ var __dirname = "/home/runner/work/nest-hex/nest-hex/src/cli/generators";
241
241
 
242
242
  class BaseGenerator {
243
243
  config;
@@ -237,7 +237,7 @@ __export(exports_base_generator, {
237
237
  });
238
238
  module.exports = __toCommonJS(exports_base_generator);
239
239
  var import_node_path2 = require("node:path");
240
- var __dirname = "C:\\Users\\liorv\\OneDrive\\Desktop\\Projects\\Personal\\nestjs-adapter\\src\\cli\\generators";
240
+ var __dirname = "/home/runner/work/nest-hex/nest-hex/src/cli/generators";
241
241
 
242
242
  class BaseGenerator {
243
243
  config;
@@ -1,3 +1,15 @@
1
+ interface PortInfo {
2
+ /** Port name in kebab-case (e.g., 'object-storage') */
3
+ name: string;
4
+ /** Port name in PascalCase (e.g., 'ObjectStorage') */
5
+ pascalName: string;
6
+ /** Token name (e.g., 'OBJECT_STORAGE_PORT') */
7
+ tokenName: string;
8
+ /** Relative import path from adapter to port token file */
9
+ tokenImportPath: string;
10
+ /** Absolute path to port directory */
11
+ portPath: string;
12
+ }
1
13
  interface ComponentOption {
2
14
  value: string;
3
15
  label: string;
@@ -39,18 +51,6 @@ interface NameInputProps {
39
51
  * Name input for component generation.
40
52
  */
41
53
  declare function NameInput({ type, step, onSubmit }: NameInputProps): JSX.Element;
42
- interface PortInfo {
43
- /** Port name in kebab-case (e.g., 'object-storage') */
44
- name: string;
45
- /** Port name in PascalCase (e.g., 'ObjectStorage') */
46
- pascalName: string;
47
- /** Token name (e.g., 'OBJECT_STORAGE_PORT') */
48
- tokenName: string;
49
- /** Relative import path from adapter to port token file */
50
- tokenImportPath: string;
51
- /** Absolute path to port directory */
52
- portPath: string;
53
- }
54
54
  interface PortSelectorProps {
55
55
  ports: PortInfo[];
56
56
  onSubmit: (portInfo: PortInfo) => void;
@@ -1037,7 +1037,11 @@ function Confirm({
1037
1037
  // src/cli/ui/components/FileProgress.tsx
1038
1038
  var import_ink3 = require("ink");
1039
1039
  var jsx_dev_runtime3 = __toESM(require_jsx_dev_runtime(), 1);
1040
- function FileProgress({ fileName, status, error }) {
1040
+ function FileProgress({
1041
+ fileName,
1042
+ status,
1043
+ error
1044
+ }) {
1041
1045
  const getStatusIcon = () => {
1042
1046
  switch (status) {
1043
1047
  case "completed":
@@ -1112,7 +1116,11 @@ function FileProgress({ fileName, status, error }) {
1112
1116
  var import_ui3 = require("@inkjs/ui");
1113
1117
  var import_ink4 = require("ink");
1114
1118
  var jsx_dev_runtime4 = __toESM(require_jsx_dev_runtime(), 1);
1115
- function NameInput({ type, step, onSubmit }) {
1119
+ function NameInput({
1120
+ type,
1121
+ step,
1122
+ onSubmit
1123
+ }) {
1116
1124
  if (type === "full") {
1117
1125
  const currentStep = step || "port";
1118
1126
  const labels = {
@@ -1203,7 +1211,10 @@ function NameInput({ type, step, onSubmit }) {
1203
1211
  var import_ui4 = require("@inkjs/ui");
1204
1212
  var import_ink5 = require("ink");
1205
1213
  var jsx_dev_runtime5 = __toESM(require_jsx_dev_runtime(), 1);
1206
- function PortSelector({ ports, onSubmit }) {
1214
+ function PortSelector({
1215
+ ports,
1216
+ onSubmit
1217
+ }) {
1207
1218
  if (ports.length === 0) {
1208
1219
  return /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(import_ink5.Box, {
1209
1220
  flexDirection: "column",
@@ -1288,7 +1299,10 @@ function PortSelector({ ports, onSubmit }) {
1288
1299
  var import_ui5 = require("@inkjs/ui");
1289
1300
  var import_ink6 = require("ink");
1290
1301
  var jsx_dev_runtime6 = __toESM(require_jsx_dev_runtime(), 1);
1291
- function ProgressIndicator({ steps, title }) {
1302
+ function ProgressIndicator({
1303
+ steps,
1304
+ title
1305
+ }) {
1292
1306
  return /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(import_ink6.Box, {
1293
1307
  flexDirection: "column",
1294
1308
  paddingY: 1,
@@ -30,31 +30,43 @@ type PortConfig<
30
30
  *
31
31
  * Adapters are dynamic modules that provide a port token and hide infrastructure details.
32
32
  * This base class automatically handles provider registration, token aliasing, and exports
33
- * by reading metadata from the @Port decorator.
33
+ * by reading metadata from the @Adapter decorator.
34
34
  *
35
35
  * @template TOptions - The options type for configuring this adapter
36
36
  *
37
37
  * @example
38
+ * Basic adapter with decorator options:
38
39
  * ```typescript
39
- * @Port({
40
- * token: STORAGE_PORT,
40
+ * @Adapter({
41
+ * portToken: STORAGE_PORT,
42
+ * implementation: S3Service,
43
+ * imports: [HttpModule],
44
+ * providers: [{ provide: 'CONFIG', useValue: {...} }]
45
+ * })
46
+ * class S3Adapter extends AdapterBase<S3Options> {}
47
+ * ```
48
+ *
49
+ * Advanced adapter with dynamic configuration:
50
+ * ```typescript
51
+ * @Adapter({
52
+ * portToken: STORAGE_PORT,
41
53
  * implementation: S3Service
42
54
  * })
43
- * class S3Adapter extends Adapter<S3Options> {
44
- * protected override imports(options: S3Options) {
45
- * return []; // Optional: import other modules
55
+ * class S3Adapter extends AdapterBase<S3Options> {
56
+ * protected imports(options: S3Options) {
57
+ * return [HttpModule.register({ timeout: options.timeout })]
46
58
  * }
47
59
  *
48
- * protected override extraPoviders(options: S3Options) {
49
- * return []; // Optional: additional providers
60
+ * protected extraProviders(options: S3Options) {
61
+ * return [{ provide: 'S3_CONFIG', useValue: options }]
50
62
  * }
51
63
  * }
52
64
  * ```
53
65
  */
54
- declare class Adapter<TOptions> {
66
+ declare class AdapterBase<TOptions> {
55
67
  /**
56
68
  * Optional hook to import other NestJS modules.
57
- * Override this method to add module dependencies.
69
+ * Override this method to add module dependencies based on options.
58
70
  *
59
71
  * @param _options - The adapter configuration options
60
72
  * @returns Array of modules to import
@@ -62,31 +74,31 @@ declare class Adapter<TOptions> {
62
74
  protected imports(_options?: TOptions): unknown[];
63
75
  /**
64
76
  * Optional hook to provide additional providers.
65
- * Override this method to add helper services, factories, or initialization logic.
77
+ * Override this method to add helper services, factories, or initialization logic based on options.
66
78
  *
67
79
  * @param _options - The adapter configuration options
68
80
  * @returns Array of additional providers
69
81
  */
70
- protected extraPoviders(_options: TOptions): Provider[];
82
+ protected extraProviders(_options: TOptions): Provider[];
71
83
  /**
72
84
  * Synchronous registration method.
73
85
  * Creates a dynamic module with the adapter's port token and implementation.
74
86
  *
75
87
  * @param options - The adapter configuration options
76
88
  * @returns An AdapterModule with compile-time token proof
77
- * @throws Error if @Port decorator is missing or incomplete
89
+ * @throws Error if @Adapter decorator is missing or incomplete
78
90
  */
79
91
  static register<
80
92
  TToken,
81
93
  TOptions
82
- >(this: new () => Adapter<TOptions>, options: TOptions): AdapterModule<TToken>;
94
+ >(this: new () => AdapterBase<TOptions>, options: TOptions): AdapterModule<TToken>;
83
95
  /**
84
96
  * Asynchronous registration method with dependency injection support.
85
97
  * Creates a dynamic module where options are resolved via DI.
86
98
  *
87
99
  * @param options - Async configuration with factory, imports, and inject
88
100
  * @returns An AdapterModule with compile-time token proof
89
- * @throws Error if @Port decorator is missing or incomplete
101
+ * @throws Error if @Adapter decorator is missing or incomplete
90
102
  *
91
103
  * @example
92
104
  * ```typescript
@@ -100,35 +112,53 @@ declare class Adapter<TOptions> {
100
112
  static registerAsync<
101
113
  TToken,
102
114
  TOptions
103
- >(this: new () => Adapter<TOptions>, options: {
115
+ >(this: new () => AdapterBase<TOptions>, options: {
104
116
  imports?: unknown[];
105
117
  inject?: unknown[];
106
118
  useFactory: (...args: unknown[]) => TOptions | Promise<TOptions>;
107
119
  }): AdapterModule<TToken>;
108
120
  }
109
- import { Type } from "@nestjs/common";
121
+ import { DynamicModule as DynamicModule2, Provider as Provider2, Type } from "@nestjs/common";
110
122
  /**
111
- * Declares the port configuration for an adapter (token and implementation).
123
+ * Declares the adapter configuration (port token, implementation, imports, and extra providers).
112
124
  *
113
- * This decorator stores both the port token and implementation class in metadata,
114
- * which is read at runtime by the Adapter base class's register() and registerAsync() methods.
125
+ * This decorator stores the port token, implementation class, optional imports, and
126
+ * optional extra providers in metadata, which is read at runtime by the Adapter base
127
+ * class's register() and registerAsync() methods.
115
128
  *
116
- * @param config - Port configuration object
117
- * @param config.token - The port token this adapter provides
129
+ * @param config - Adapter configuration object
130
+ * @param config.portToken - The port token this adapter provides
118
131
  * @param config.implementation - The concrete implementation class that provides the port functionality
132
+ * @param config.imports - Optional array of NestJS modules to import (module classes or DynamicModule objects)
133
+ * @param config.providers - Optional array of additional providers to register
119
134
  *
120
135
  * @example
136
+ * Basic adapter:
121
137
  * ```typescript
122
- * @Port({
123
- * token: OBJECT_STORAGE_PORT,
138
+ * @Adapter({
139
+ * portToken: OBJECT_STORAGE_PORT,
124
140
  * implementation: S3ObjectStorageService
125
141
  * })
126
- * class S3Adapter extends Adapter<S3Options> {}
142
+ * class S3Adapter extends AdapterBase<S3Options> {}
143
+ * ```
144
+ *
145
+ * @example
146
+ * Adapter with imports and extra providers:
147
+ * ```typescript
148
+ * @Adapter({
149
+ * portToken: HTTP_CLIENT_PORT,
150
+ * implementation: AxiosHttpClient,
151
+ * imports: [HttpModule],
152
+ * providers: [{ provide: 'HTTP_CONFIG', useValue: { timeout: 5000 } }]
153
+ * })
154
+ * class AxiosAdapter extends AdapterBase<AxiosOptions> {}
127
155
  * ```
128
156
  */
129
- declare function Port2<C extends PortConfig<any, any>>(config: {
130
- token: C["token"];
157
+ declare function Adapter<C extends PortConfig<any, any>>(config: {
158
+ portToken: C["token"];
131
159
  implementation: Type<C["port"]>;
160
+ imports?: Array<Type<any> | DynamicModule2 | Promise<DynamicModule2>>;
161
+ providers?: Provider2[];
132
162
  }): ClassDecorator;
133
163
  /**
134
164
  * DX decorator for injecting a port token into service constructors.
@@ -148,7 +178,7 @@ declare function Port2<C extends PortConfig<any, any>>(config: {
148
178
  * ```
149
179
  */
150
180
  declare function InjectPort<TToken>(token: TToken): ParameterDecorator;
151
- import { DynamicModule as DynamicModule2 } from "@nestjs/common";
181
+ import { DynamicModule as DynamicModule3 } from "@nestjs/common";
152
182
  declare class PortModule<_TService> {
153
183
  /**
154
184
  * Registers the port module with an adapter.
@@ -159,6 +189,6 @@ declare class PortModule<_TService> {
159
189
  */
160
190
  static register<TToken>({ adapter }: {
161
191
  adapter?: AdapterModule<TToken>;
162
- }): DynamicModule2;
192
+ }): DynamicModule3;
163
193
  }
164
- export { PortModule, PortConfig, Port2 as Port, InjectPort, AdapterModule, Adapter };
194
+ export { PortModule, PortConfig, InjectPort, AdapterModule, AdapterBase, Adapter };
package/dist/src/index.js CHANGED
@@ -55,8 +55,8 @@ var __legacyDecorateClassTS = function(decorators, target, key, desc) {
55
55
  var exports_src = {};
56
56
  __export(exports_src, {
57
57
  PortModule: () => PortModule,
58
- Port: () => Port,
59
58
  InjectPort: () => InjectPort,
59
+ AdapterBase: () => AdapterBase,
60
60
  Adapter: () => Adapter
61
61
  });
62
62
  module.exports = __toCommonJS(exports_src);
@@ -67,32 +67,37 @@ var import_reflect_metadata = require("reflect-metadata");
67
67
  // src/core/constants.ts
68
68
  var PORT_TOKEN_METADATA = Symbol("PORT_TOKEN_METADATA");
69
69
  var PORT_IMPLEMENTATION_METADATA = Symbol("PORT_IMPLEMENTATION_METADATA");
70
+ var ADAPTER_IMPORTS_METADATA = Symbol("ADAPTER_IMPORTS_METADATA");
71
+ var ADAPTER_PROVIDERS_METADATA = Symbol("ADAPTER_PROVIDERS_METADATA");
70
72
 
71
73
  // src/core/adapter.base.ts
72
- class Adapter {
74
+ class AdapterBase {
73
75
  imports(_options) {
74
76
  return [];
75
77
  }
76
- extraPoviders(_options) {
78
+ extraProviders(_options) {
77
79
  return [];
78
80
  }
79
81
  static register(options) {
80
82
  const instance = new this;
81
83
  const token = Reflect.getMetadata(PORT_TOKEN_METADATA, this);
82
84
  const implementation = Reflect.getMetadata(PORT_IMPLEMENTATION_METADATA, this);
85
+ const decoratorImports = Reflect.getMetadata(ADAPTER_IMPORTS_METADATA, this) ?? [];
86
+ const decoratorProviders = Reflect.getMetadata(ADAPTER_PROVIDERS_METADATA, this) ?? [];
83
87
  if (!token) {
84
- throw new Error(`${this.name} must be decorated with @Port() and specify 'token'`);
88
+ throw new Error(`${this.name} must be decorated with @Adapter() and specify 'portToken'`);
85
89
  }
86
90
  if (!implementation) {
87
- throw new Error(`${this.name} must be decorated with @Port() and specify 'implementation'`);
91
+ throw new Error(`${this.name} must be decorated with @Adapter() and specify 'implementation'`);
88
92
  }
89
93
  return {
90
94
  module: this,
91
- imports: instance.imports(options),
95
+ imports: [...decoratorImports, ...instance.imports(options)],
92
96
  providers: [
93
97
  implementation,
94
98
  { provide: token, useExisting: implementation },
95
- ...instance.extraPoviders(options)
99
+ ...decoratorProviders,
100
+ ...instance.extraProviders(options)
96
101
  ],
97
102
  exports: [token],
98
103
  __provides: token
@@ -102,19 +107,26 @@ class Adapter {
102
107
  const instance = new this;
103
108
  const token = Reflect.getMetadata(PORT_TOKEN_METADATA, this);
104
109
  const implementation = Reflect.getMetadata(PORT_IMPLEMENTATION_METADATA, this);
110
+ const decoratorImports = Reflect.getMetadata(ADAPTER_IMPORTS_METADATA, this) ?? [];
111
+ const decoratorProviders = Reflect.getMetadata(ADAPTER_PROVIDERS_METADATA, this) ?? [];
105
112
  if (!token) {
106
- throw new Error(`${this.name} must be decorated with @Port() and specify 'token'`);
113
+ throw new Error(`${this.name} must be decorated with @Adapter() and specify 'portToken'`);
107
114
  }
108
115
  if (!implementation) {
109
- throw new Error(`${this.name} must be decorated with @Port() and specify 'implementation'`);
116
+ throw new Error(`${this.name} must be decorated with @Adapter() and specify 'implementation'`);
110
117
  }
111
118
  return {
112
119
  module: this,
113
- imports: [...options.imports ?? [], ...instance.imports()],
120
+ imports: [
121
+ ...decoratorImports,
122
+ ...instance.imports(),
123
+ ...options.imports ?? []
124
+ ],
114
125
  providers: [
115
126
  implementation,
116
127
  { provide: token, useExisting: implementation },
117
- ...instance.extraPoviders({})
128
+ ...decoratorProviders,
129
+ ...instance.extraProviders({})
118
130
  ],
119
131
  exports: [token],
120
132
  __provides: token
@@ -124,10 +136,16 @@ class Adapter {
124
136
  // src/core/decorators.ts
125
137
  var import_reflect_metadata2 = require("reflect-metadata");
126
138
  var import_common = require("@nestjs/common");
127
- function Port(config) {
139
+ function Adapter(config) {
128
140
  return (target) => {
129
- Reflect.defineMetadata(PORT_TOKEN_METADATA, config.token, target);
141
+ Reflect.defineMetadata(PORT_TOKEN_METADATA, config.portToken, target);
130
142
  Reflect.defineMetadata(PORT_IMPLEMENTATION_METADATA, config.implementation, target);
143
+ if (config.imports) {
144
+ Reflect.defineMetadata(ADAPTER_IMPORTS_METADATA, config.imports, target);
145
+ }
146
+ if (config.providers) {
147
+ Reflect.defineMetadata(ADAPTER_PROVIDERS_METADATA, config.providers, target);
148
+ }
131
149
  };
132
150
  }
133
151
  function InjectPort(token) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nest-hex",
3
- "version": "0.2.2",
3
+ "version": "0.3.2",
4
4
  "description": "A tiny NestJS-native library for building pluggable adapters (Ports & Adapters / Hexagonal) using class-based Dynamic Modules, with great DX and strong type safety.",
5
5
  "homepage": "https://github.com/LiorVainer/nest-hex#readme",
6
6
  "bugs": {