nest-hex 0.2.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 LiorVainer
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,425 @@
1
+ # nest-hex
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.
4
+
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
+
7
+ ## Why?
8
+
9
+ Building NestJS applications with the Ports & Adapters pattern involves repetitive boilerplate:
10
+
11
+ - Registering concrete implementation classes
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)
16
+
17
+ This library provides base classes and decorators to eliminate this boilerplate while maintaining compile-time type safety.
18
+
19
+ ## Features
20
+
21
+ - ๐ŸŽฏ **Declarative**: Declare port tokens and implementations once using `@Port({ token, implementation })`
22
+ - ๐Ÿ—๏ธ **Class-based**: Use standard NestJS dynamic modules, no function factories required
23
+ - ๐Ÿ”’ **Type-safe**: `AdapterModule<TToken>` carries compile-time proof of which token it provides
24
+ - โšก **Zero runtime overhead**: Uses TypeScript decorators and metadata, minimal abstraction
25
+ - ๐Ÿ“ฆ **Tiny**: Core library is under 1KB minified
26
+ - ๐Ÿงช **Testable**: Easily mock adapters for testing
27
+
28
+ ## Installation
29
+
30
+ ```bash
31
+ npm install nest-hex
32
+ # or
33
+ yarn add nest-hex
34
+ # or
35
+ pnpm add nest-hex
36
+ # or
37
+ bun add nest-hex
38
+ ```
39
+
40
+ ### Peer Dependencies
41
+
42
+ ```bash
43
+ npm install @nestjs/common @nestjs/core reflect-metadata
44
+ ```
45
+
46
+ ## Quick Start
47
+
48
+ ### 1. Define a Port (Domain Interface)
49
+
50
+ ```typescript
51
+ // storage.port.ts
52
+ export const STORAGE_PORT = Symbol('STORAGE_PORT');
53
+
54
+ export interface StoragePort {
55
+ upload(file: Buffer, key: string): Promise<{ url: string }>;
56
+ download(key: string): Promise<Buffer>;
57
+ }
58
+ ```
59
+
60
+ ### 2. Create an Adapter (Infrastructure Implementation)
61
+
62
+ ```typescript
63
+ // s3.adapter.ts
64
+ import { Injectable } from '@nestjs/common';
65
+ import { Adapter, Port } from 'nest-hex';
66
+ import { STORAGE_PORT, type StoragePort } from './storage.port';
67
+
68
+ // Implementation service
69
+ @Injectable()
70
+ class S3StorageService implements StoragePort {
71
+ async upload(file: Buffer, key: string) {
72
+ // AWS S3 upload logic here
73
+ return { url: `https://s3.amazonaws.com/bucket/${key}` };
74
+ }
75
+
76
+ async download(key: string) {
77
+ // AWS S3 download logic here
78
+ return Buffer.from('file contents');
79
+ }
80
+ }
81
+
82
+ // Adapter configuration
83
+ interface S3Options {
84
+ bucket: string;
85
+ region: string;
86
+ accessKeyId?: string;
87
+ secretAccessKey?: string;
88
+ }
89
+
90
+ // Adapter module - single decorator declares everything!
91
+ @Port({
92
+ token: STORAGE_PORT,
93
+ implementation: S3StorageService,
94
+ })
95
+ export class S3Adapter extends Adapter<S3Options> {}
96
+ ```
97
+
98
+ ### 3. Create a Port Module (Domain Service)
99
+
100
+ ```typescript
101
+ // storage.module.ts
102
+ import { Injectable, Module } from '@nestjs/common';
103
+ import { InjectPort, PortModule } from 'nest-hex';
104
+ import { STORAGE_PORT, type StoragePort } from './storage.port';
105
+
106
+ // Domain service that uses the port
107
+ @Injectable()
108
+ export class StorageService {
109
+ constructor(
110
+ @InjectPort(STORAGE_PORT)
111
+ private readonly storage: StoragePort,
112
+ ) {}
113
+
114
+ async uploadUserAvatar(userId: string, image: Buffer) {
115
+ const key = `avatars/${userId}.jpg`;
116
+ return this.storage.upload(image, key);
117
+ }
118
+
119
+ async downloadUserAvatar(userId: string) {
120
+ const key = `avatars/${userId}.jpg`;
121
+ return this.storage.download(key);
122
+ }
123
+ }
124
+
125
+ // Port module that accepts any adapter
126
+ @Module({})
127
+ export class StorageModule extends PortModule<typeof StorageService> {}
128
+ ```
129
+
130
+ ### 4. Wire It Up in Your App
131
+
132
+ ```typescript
133
+ // app.module.ts
134
+ import { Module } from '@nestjs/common';
135
+ import { StorageModule } from './storage/storage.module';
136
+ import S3Adapter from './storage/adapters/s3.adapter';
137
+
138
+ @Module({
139
+ imports: [
140
+ StorageModule.register({
141
+ adapter: S3Adapter.register({
142
+ bucket: 'my-app-uploads',
143
+ region: 'us-east-1',
144
+ accessKeyId: process.env.AWS_ACCESS_KEY_ID,
145
+ secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
146
+ }),
147
+ }),
148
+ ],
149
+ })
150
+ export class AppModule {}
151
+ ```
152
+
153
+ That's it! You now have a fully type-safe, pluggable storage adapter. ๐ŸŽ‰
154
+
155
+ ## Key Benefits
156
+
157
+ ### Before (Manual Boilerplate)
158
+
159
+ ```typescript
160
+ // Lots of manual wiring...
161
+ @Module({})
162
+ export class S3StorageModule {
163
+ static register(options: S3Options): DynamicModule {
164
+ return {
165
+ module: S3StorageModule,
166
+ providers: [
167
+ S3StorageService,
168
+ { provide: STORAGE_PORT, useExisting: S3StorageService },
169
+ // More boilerplate...
170
+ ],
171
+ exports: [STORAGE_PORT],
172
+ };
173
+ }
174
+ }
175
+ ```
176
+
177
+ ### After (With nest-hex)
178
+
179
+ ```typescript
180
+ // Clean and declarative!
181
+ @Port({
182
+ token: STORAGE_PORT,
183
+ implementation: S3StorageService,
184
+ })
185
+ export class S3Adapter extends Adapter<S3Options> {}
186
+ ```
187
+
188
+ ## Advanced Usage
189
+
190
+ ### Async Registration with Dependency Injection
191
+
192
+ ```typescript
193
+ import { ConfigModule, ConfigService } from '@nestjs/config';
194
+
195
+ @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
+ ],
210
+ })
211
+ export class AppModule {}
212
+ ```
213
+
214
+ ### Custom Imports and Extra Ports
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
+ ```
241
+
242
+ ### Swapping Adapters
243
+
244
+ The beauty of the Ports & Adapters pattern is that you can easily swap implementations:
245
+
246
+ ```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
+ @Module({
258
+ imports: [
259
+ StorageModule.register({ adapter }),
260
+ ],
261
+ })
262
+ export class AppModule {}
263
+ ```
264
+
265
+ ### Testing with Mock Adapters
266
+
267
+ ```typescript
268
+ import { Adapter, Port } from 'nest-hex';
269
+
270
+ class MockStorageService {
271
+ async upload(file: Buffer, key: string) {
272
+ return { url: `mock://storage/${key}` };
273
+ }
274
+
275
+ async download(key: string) {
276
+ return Buffer.from('mock file contents');
277
+ }
278
+ }
279
+
280
+ @Port({
281
+ token: STORAGE_PORT,
282
+ implementation: MockStorageService,
283
+ })
284
+ export class MockStorageAdapter extends Adapter<void> {}
285
+
286
+ // Use in tests
287
+ const module = await Test.createTestingModule({
288
+ imports: [
289
+ StorageModule.register({
290
+ adapter: MockStorageAdapter.register(undefined),
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
+ };
360
+ ```
361
+
362
+ ## Best Practices
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
+ ```
393
+
394
+ - **Don't use `process.env` in adapters**
395
+ ```typescript
396
+ // โŒ Bad: Config hard-coded in adapter
397
+ class S3Adapter {
398
+ bucket = process.env.S3_BUCKET;
399
+ }
400
+ ```
401
+
402
+ - **Don't mix domain logic with adapters**
403
+ - Adapters = infrastructure only
404
+ - Domain logic = port modules/services
405
+
406
+ ## Examples
407
+
408
+ See the [`examples/`](./examples) directory for complete working examples:
409
+
410
+ - **Object Storage** - S3 adapter with file upload/download
411
+ - **Currency Rates** - HTTP API adapter with rate conversion
412
+ - **Basic Examples** - Decorator usage patterns
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
418
+
419
+ ## License
420
+
421
+ MIT
422
+
423
+ ## Contributing
424
+
425
+ Contributions are welcome! Please see [CONTRIBUTING.md](./CONTRIBUTING.md) for contribution guidelines.
@@ -0,0 +1,164 @@
1
+ import { Provider } from "@nestjs/common";
2
+ import { DynamicModule } from "@nestjs/common";
3
+ /**
4
+ * A DynamicModule that carries a compile-time proof it provides `TToken`.
5
+ *
6
+ * The `__provides` field is used only for TypeScript structural typing to ensure
7
+ * that feature modules receive adapters that provide the correct token type.
8
+ * This enables compile-time type safety without runtime overhead.
9
+ *
10
+ * @template TToken - The token type that this adapter module provides
11
+ *
12
+ * @example
13
+ * ```typescript
14
+ * const s3Module: AdapterModule<typeof STORAGE_TOKEN> = S3Adapter.register(options);
15
+ * // TypeScript ensures s3Module provides STORAGE_TOKEN
16
+ * ```
17
+ */
18
+ type AdapterModule<TToken> = DynamicModule & {
19
+ __provides: TToken;
20
+ };
21
+ type PortConfig<
22
+ Token,
23
+ Port
24
+ > = {
25
+ token: Token;
26
+ port: Port;
27
+ };
28
+ /**
29
+ * Abstract base class for building NestJS adapter modules following the Ports & Adapters pattern.
30
+ *
31
+ * Adapters are dynamic modules that provide a port token and hide infrastructure details.
32
+ * This base class automatically handles provider registration, token aliasing, and exports
33
+ * by reading metadata from the @Port decorator.
34
+ *
35
+ * @template TOptions - The options type for configuring this adapter
36
+ *
37
+ * @example
38
+ * ```typescript
39
+ * @Port({
40
+ * token: STORAGE_PORT,
41
+ * implementation: S3Service
42
+ * })
43
+ * class S3Adapter extends Adapter<S3Options> {
44
+ * protected override imports(options: S3Options) {
45
+ * return []; // Optional: import other modules
46
+ * }
47
+ *
48
+ * protected override extraPoviders(options: S3Options) {
49
+ * return []; // Optional: additional providers
50
+ * }
51
+ * }
52
+ * ```
53
+ */
54
+ declare class Adapter<TOptions> {
55
+ /**
56
+ * Optional hook to import other NestJS modules.
57
+ * Override this method to add module dependencies.
58
+ *
59
+ * @param _options - The adapter configuration options
60
+ * @returns Array of modules to import
61
+ */
62
+ protected imports(_options?: TOptions): unknown[];
63
+ /**
64
+ * Optional hook to provide additional providers.
65
+ * Override this method to add helper services, factories, or initialization logic.
66
+ *
67
+ * @param _options - The adapter configuration options
68
+ * @returns Array of additional providers
69
+ */
70
+ protected extraPoviders(_options: TOptions): Provider[];
71
+ /**
72
+ * Synchronous registration method.
73
+ * Creates a dynamic module with the adapter's port token and implementation.
74
+ *
75
+ * @param options - The adapter configuration options
76
+ * @returns An AdapterModule with compile-time token proof
77
+ * @throws Error if @Port decorator is missing or incomplete
78
+ */
79
+ static register<
80
+ TToken,
81
+ TOptions
82
+ >(this: new () => Adapter<TOptions>, options: TOptions): AdapterModule<TToken>;
83
+ /**
84
+ * Asynchronous registration method with dependency injection support.
85
+ * Creates a dynamic module where options are resolved via DI.
86
+ *
87
+ * @param options - Async configuration with factory, imports, and inject
88
+ * @returns An AdapterModule with compile-time token proof
89
+ * @throws Error if @Port decorator is missing or incomplete
90
+ *
91
+ * @example
92
+ * ```typescript
93
+ * S3Adapter.registerAsync({
94
+ * imports: [ConfigModule],
95
+ * inject: [ConfigService],
96
+ * useFactory: (config) => config.get('s3'),
97
+ * })
98
+ * ```
99
+ */
100
+ static registerAsync<
101
+ TToken,
102
+ TOptions
103
+ >(this: new () => Adapter<TOptions>, options: {
104
+ imports?: unknown[];
105
+ inject?: unknown[];
106
+ useFactory: (...args: unknown[]) => TOptions | Promise<TOptions>;
107
+ }): AdapterModule<TToken>;
108
+ }
109
+ import { Type } from "@nestjs/common";
110
+ /**
111
+ * Declares the port configuration for an adapter (token and implementation).
112
+ *
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.
115
+ *
116
+ * @param config - Port configuration object
117
+ * @param config.token - The port token this adapter provides
118
+ * @param config.implementation - The concrete implementation class that provides the port functionality
119
+ *
120
+ * @example
121
+ * ```typescript
122
+ * @Port({
123
+ * token: OBJECT_STORAGE_PORT,
124
+ * implementation: S3ObjectStorageService
125
+ * })
126
+ * class S3Adapter extends Adapter<S3Options> {}
127
+ * ```
128
+ */
129
+ declare function Port2<C extends PortConfig<any, any>>(config: {
130
+ token: C["token"];
131
+ implementation: Type<C["port"]>;
132
+ }): ClassDecorator;
133
+ /**
134
+ * DX decorator for injecting a port token into service constructors.
135
+ * This is a shorthand for @Inject(token) that provides clearer semantics.
136
+ *
137
+ * @param token - The port token to inject
138
+ *
139
+ * @example
140
+ * ```typescript
141
+ * @Injectable()
142
+ * class ObjectStorageService {
143
+ * constructor(
144
+ * @InjectPort(OBJECT_STORAGE_PROVIDER)
145
+ * private readonly storage: ObjectStoragePort,
146
+ * ) {}
147
+ * }
148
+ * ```
149
+ */
150
+ declare function InjectPort<TToken>(token: TToken): ParameterDecorator;
151
+ import { DynamicModule as DynamicModule2 } from "@nestjs/common";
152
+ declare class PortModule<_TService> {
153
+ /**
154
+ * Registers the port module with an adapter.
155
+ *
156
+ * @param config - Configuration object containing the adapter module
157
+ * @param config.adapter - An adapter module that provides the required port
158
+ * @returns A dynamic module that imports the adapter and provides the service
159
+ */
160
+ static register<TToken>({ adapter }: {
161
+ adapter?: AdapterModule<TToken>;
162
+ }): DynamicModule2;
163
+ }
164
+ export { PortModule, PortConfig, Port2 as Port, InjectPort, AdapterModule, Adapter };
package/dist/index.js ADDED
@@ -0,0 +1,137 @@
1
+ var import_node_module = require("node:module");
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __moduleCache = /* @__PURE__ */ new WeakMap;
7
+ var __toCommonJS = (from) => {
8
+ var entry = __moduleCache.get(from), desc;
9
+ if (entry)
10
+ return entry;
11
+ entry = __defProp({}, "__esModule", { value: true });
12
+ if (from && typeof from === "object" || typeof from === "function")
13
+ __getOwnPropNames(from).map((key) => !__hasOwnProp.call(entry, key) && __defProp(entry, key, {
14
+ get: () => from[key],
15
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
16
+ }));
17
+ __moduleCache.set(from, entry);
18
+ return entry;
19
+ };
20
+ var __export = (target, all) => {
21
+ for (var name in all)
22
+ __defProp(target, name, {
23
+ get: all[name],
24
+ enumerable: true,
25
+ configurable: true,
26
+ set: (newValue) => all[name] = () => newValue
27
+ });
28
+ };
29
+ var __legacyDecorateClassTS = function(decorators, target, key, desc) {
30
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
31
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function")
32
+ r = Reflect.decorate(decorators, target, key, desc);
33
+ else
34
+ for (var i = decorators.length - 1;i >= 0; i--)
35
+ if (d = decorators[i])
36
+ r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
37
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
38
+ };
39
+
40
+ // src/index.ts
41
+ var exports_src = {};
42
+ __export(exports_src, {
43
+ PortModule: () => PortModule,
44
+ Port: () => Port,
45
+ InjectPort: () => InjectPort,
46
+ Adapter: () => Adapter
47
+ });
48
+ module.exports = __toCommonJS(exports_src);
49
+
50
+ // src/core/adapter.base.ts
51
+ var import_reflect_metadata = require("reflect-metadata");
52
+
53
+ // src/core/constants.ts
54
+ var PORT_TOKEN_METADATA = Symbol("PORT_TOKEN_METADATA");
55
+ var PORT_IMPLEMENTATION_METADATA = Symbol("PORT_IMPLEMENTATION_METADATA");
56
+
57
+ // src/core/adapter.base.ts
58
+ class Adapter {
59
+ imports(_options) {
60
+ return [];
61
+ }
62
+ extraPoviders(_options) {
63
+ return [];
64
+ }
65
+ static register(options) {
66
+ const instance = new this;
67
+ const token = Reflect.getMetadata(PORT_TOKEN_METADATA, this);
68
+ const implementation = Reflect.getMetadata(PORT_IMPLEMENTATION_METADATA, this);
69
+ if (!token) {
70
+ throw new Error(`${this.name} must be decorated with @Port() and specify 'token'`);
71
+ }
72
+ if (!implementation) {
73
+ throw new Error(`${this.name} must be decorated with @Port() and specify 'implementation'`);
74
+ }
75
+ return {
76
+ module: this,
77
+ imports: instance.imports(options),
78
+ providers: [
79
+ implementation,
80
+ { provide: token, useExisting: implementation },
81
+ ...instance.extraPoviders(options)
82
+ ],
83
+ exports: [token],
84
+ __provides: token
85
+ };
86
+ }
87
+ static registerAsync(options) {
88
+ const instance = new this;
89
+ const token = Reflect.getMetadata(PORT_TOKEN_METADATA, this);
90
+ const implementation = Reflect.getMetadata(PORT_IMPLEMENTATION_METADATA, this);
91
+ if (!token) {
92
+ throw new Error(`${this.name} must be decorated with @Port() and specify 'token'`);
93
+ }
94
+ if (!implementation) {
95
+ throw new Error(`${this.name} must be decorated with @Port() and specify 'implementation'`);
96
+ }
97
+ return {
98
+ module: this,
99
+ imports: [...options.imports ?? [], ...instance.imports()],
100
+ providers: [
101
+ implementation,
102
+ { provide: token, useExisting: implementation },
103
+ ...instance.extraPoviders({})
104
+ ],
105
+ exports: [token],
106
+ __provides: token
107
+ };
108
+ }
109
+ }
110
+ // src/core/decorators.ts
111
+ var import_reflect_metadata2 = require("reflect-metadata");
112
+ var import_common = require("@nestjs/common");
113
+ function Port(config) {
114
+ return (target) => {
115
+ Reflect.defineMetadata(PORT_TOKEN_METADATA, config.token, target);
116
+ Reflect.defineMetadata(PORT_IMPLEMENTATION_METADATA, config.implementation, target);
117
+ };
118
+ }
119
+ function InjectPort(token) {
120
+ return import_common.Inject(token);
121
+ }
122
+ // src/core/port-module.base.ts
123
+ var import_reflect_metadata3 = require("reflect-metadata");
124
+ var import_common2 = require("@nestjs/common");
125
+ class PortModule {
126
+ static register({
127
+ adapter
128
+ }) {
129
+ return {
130
+ module: PortModule,
131
+ imports: adapter ? [adapter] : []
132
+ };
133
+ }
134
+ }
135
+ PortModule = __legacyDecorateClassTS([
136
+ import_common2.Module({})
137
+ ], PortModule);
package/package.json ADDED
@@ -0,0 +1,88 @@
1
+ {
2
+ "name": "nest-hex",
3
+ "version": "0.2.0",
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
+ "homepage": "https://github.com/LiorVainer/nest-hex#readme",
6
+ "bugs": {
7
+ "url": "https://github.com/LiorVainer/nest-hex/issues"
8
+ },
9
+ "license": "MIT",
10
+ "publishConfig": {
11
+ "access": "public"
12
+ },
13
+ "files": [
14
+ "dist"
15
+ ],
16
+ "bin": {
17
+ "nest-hex": "./dist/cli/bin.js"
18
+ },
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "git+https://github.com/LiorVainer/nest-hex.git"
22
+ },
23
+ "scripts": {
24
+ "build": "bunup",
25
+ "dev": "bunup --watch",
26
+ "prepare": "bun simple-git-hooks",
27
+ "cli": "bun run src/cli/bin.ts",
28
+ "cli:init": "bun run src/cli/bin.ts init",
29
+ "cli:generate": "bun run src/cli/bin.ts generate",
30
+ "lint": "biome check .",
31
+ "lint:fix": "biome check --write .",
32
+ "release": "bumpp --commit --push --tag",
33
+ "test": "bun test",
34
+ "test:coverage": "bun test --coverage",
35
+ "test:watch": "bun test --watch",
36
+ "type-check": "tsc --noEmit",
37
+ "type-check:examples": "tsc --noEmit --project examples/tsconfig.json",
38
+ "vibecheck": "npx vibechck ."
39
+ },
40
+ "devDependencies": {
41
+ "@biomejs/biome": "^2.3.10",
42
+ "@inkjs/ui": "^2.0.0",
43
+ "@nestjs/common": "^11.0.0",
44
+ "@nestjs/testing": "^11.1.11",
45
+ "@types/bun": "^1.3.5",
46
+ "@types/react": "^18.3.12",
47
+ "bumpp": "^10.3.2",
48
+ "bunup": "^0.16.11",
49
+ "commander": "^12.0.0",
50
+ "handlebars": "^4.7.8",
51
+ "ink": "^5.0.1",
52
+ "react": "^18.3.1",
53
+ "reflect-metadata": "^0.2.0",
54
+ "simple-git-hooks": "^2.13.1",
55
+ "typescript": "^5.9.3"
56
+ },
57
+ "peerDependencies": {
58
+ "@nestjs/common": ">=10.0.0",
59
+ "reflect-metadata": ">=0.1.13",
60
+ "typescript": ">=4.5.0"
61
+ },
62
+ "peerDependenciesMeta": {
63
+ "typescript": {
64
+ "optional": true
65
+ }
66
+ },
67
+ "main": "./dist/index.js",
68
+ "types": "./dist/index.d.ts",
69
+ "exports": {
70
+ ".": {
71
+ "types": "./dist/index.d.ts",
72
+ "require": "./dist/index.js",
73
+ "import": "./dist/index.js"
74
+ },
75
+ "./cli": {
76
+ "types": "./dist/cli/index.d.ts",
77
+ "require": "./dist/cli/index.js",
78
+ "import": "./dist/cli/index.js"
79
+ },
80
+ "./package.json": "./package.json"
81
+ },
82
+ "simple-git-hooks": {
83
+ "pre-commit": "bun run lint:fix && bun run type-check"
84
+ },
85
+ "dependencies": {
86
+ "ts-deepmerge": "^7.0.3"
87
+ }
88
+ }