@jdevel/tnest 0.0.2 → 0.0.4

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.
Files changed (57) hide show
  1. package/README.md +260 -86
  2. package/dist/__tests__/constants.spec.d.ts +1 -0
  3. package/dist/__tests__/constants.spec.js +21 -0
  4. package/dist/__tests__/constants.spec.js.map +1 -0
  5. package/dist/__tests__/tnest-module.spec.d.ts +1 -0
  6. package/dist/__tests__/tnest-module.spec.js +88 -0
  7. package/dist/__tests__/tnest-module.spec.js.map +1 -0
  8. package/dist/client/__tests__/typed-client-factory.spec.d.ts +1 -0
  9. package/dist/client/__tests__/typed-client-factory.spec.js +35 -0
  10. package/dist/client/__tests__/typed-client-factory.spec.js.map +1 -0
  11. package/dist/client/__tests__/typed-client-types.spec.d.ts +1 -0
  12. package/dist/client/__tests__/typed-client-types.spec.js +18 -0
  13. package/dist/client/__tests__/typed-client-types.spec.js.map +1 -0
  14. package/dist/client/__tests__/typed-client.spec.d.ts +1 -0
  15. package/dist/client/__tests__/typed-client.spec.js +59 -0
  16. package/dist/client/__tests__/typed-client.spec.js.map +1 -0
  17. package/dist/contracts/__tests__/contract-types.spec.d.ts +1 -0
  18. package/dist/contracts/__tests__/contract-types.spec.js +91 -0
  19. package/dist/contracts/__tests__/contract-types.spec.js.map +1 -0
  20. package/dist/contracts/__tests__/define-helpers.spec.d.ts +1 -0
  21. package/dist/contracts/__tests__/define-helpers.spec.js +52 -0
  22. package/dist/contracts/__tests__/define-helpers.spec.js.map +1 -0
  23. package/dist/contracts/define-helpers.js.map +1 -1
  24. package/dist/handlers/__tests__/handler-types.spec.d.ts +1 -0
  25. package/dist/handlers/__tests__/handler-types.spec.js +17 -0
  26. package/dist/handlers/__tests__/handler-types.spec.js.map +1 -0
  27. package/dist/handlers/__tests__/typed-event-pattern.spec.d.ts +1 -0
  28. package/dist/handlers/__tests__/typed-event-pattern.spec.js +61 -0
  29. package/dist/handlers/__tests__/typed-event-pattern.spec.js.map +1 -0
  30. package/dist/handlers/__tests__/typed-message-pattern.spec.d.ts +1 -0
  31. package/dist/handlers/__tests__/typed-message-pattern.spec.js +94 -0
  32. package/dist/handlers/__tests__/typed-message-pattern.spec.js.map +1 -0
  33. package/dist/handlers/typed-event-pattern.decorator.d.ts +4 -2
  34. package/dist/handlers/typed-event-pattern.decorator.js.map +1 -1
  35. package/dist/handlers/typed-message-pattern.decorator.d.ts +5 -2
  36. package/dist/handlers/typed-message-pattern.decorator.js.map +1 -1
  37. package/dist/index.d.ts +1 -1
  38. package/dist/index.js.map +1 -1
  39. package/dist/interfaces/module-options.d.ts +1 -0
  40. package/dist/serialization/__tests__/default-serializer.spec.d.ts +1 -0
  41. package/dist/serialization/__tests__/default-serializer.spec.js +32 -0
  42. package/dist/serialization/__tests__/default-serializer.spec.js.map +1 -0
  43. package/dist/testing/__tests__/mock-typed-client.spec.d.ts +1 -0
  44. package/dist/testing/__tests__/mock-typed-client.spec.js +70 -0
  45. package/dist/testing/__tests__/mock-typed-client.spec.js.map +1 -0
  46. package/dist/testing/__tests__/test-contract-module.spec.d.ts +1 -0
  47. package/dist/testing/__tests__/test-contract-module.spec.js +30 -0
  48. package/dist/testing/__tests__/test-contract-module.spec.js.map +1 -0
  49. package/dist/testing/mock-typed-client.js.map +1 -1
  50. package/dist/tnest.module.d.ts +1 -0
  51. package/dist/tnest.module.js +21 -20
  52. package/dist/tnest.module.js.map +1 -1
  53. package/dist/validation/__tests__/validate-contract.spec.d.ts +1 -0
  54. package/dist/validation/__tests__/validate-contract.spec.js +102 -0
  55. package/dist/validation/__tests__/validate-contract.spec.js.map +1 -0
  56. package/dist/validation/validate-contract.decorator.js.map +1 -1
  57. package/package.json +30 -5
package/README.md CHANGED
@@ -30,11 +30,11 @@ npm install @nestjs/common @nestjs/microservices reflect-metadata rxjs
30
30
 
31
31
  Contracts describe the messages exchanged between services. There are three kinds:
32
32
 
33
- | Type | Purpose | Has response? |
34
- |------|---------|---------------|
35
- | `Command` | Write operation (request/response) | Yes |
36
- | `Query` | Read operation (request/response) | Yes |
37
- | `Event` | Notification (fire-and-forget) | No |
33
+ | Type | Purpose | Has response? |
34
+ | --------- | ---------------------------------- | ------------- |
35
+ | `Command` | Write operation (request/response) | Yes |
36
+ | `Query` | Read operation (request/response) | Yes |
37
+ | `Event` | Notification (fire-and-forget) | No |
38
38
 
39
39
  ```ts
40
40
  // contracts/user.contracts.ts
@@ -63,9 +63,9 @@ export type UserContracts = typeof userContracts;
63
63
  You can also define contracts with explicit interfaces if you prefer:
64
64
 
65
65
  ```ts
66
- import type { Command, Event, Query } from '@jdevel/tnest';
66
+ import type { Command, Event, Query, ContractRegistry } from '@jdevel/tnest';
67
67
 
68
- export interface UserContracts {
68
+ export interface UserContracts extends ContractRegistry {
69
69
  'user.create': Command<'user.create', CreateUserDto, User>;
70
70
  'user.created': Event<'user.created', { userId: string; email: string }>;
71
71
  'user.get': Query<'user.get', { id: string }, User>;
@@ -74,6 +74,10 @@ export interface UserContracts {
74
74
 
75
75
  ### 2. Register the module
76
76
 
77
+ #### Static configuration with `forRoot`
78
+
79
+ Use `forRoot` when your transport configuration is known at compile time:
80
+
77
81
  ```ts
78
82
  // app.module.ts
79
83
  import { Module } from '@nestjs/common';
@@ -91,6 +95,13 @@ import { TnestModule } from '@jdevel/tnest';
91
95
  options: { host: 'localhost', port: 3001 },
92
96
  },
93
97
  },
98
+ {
99
+ name: 'NOTIFICATION_SERVICE',
100
+ options: {
101
+ transport: Transport.REDIS,
102
+ options: { host: 'localhost', port: 6379 },
103
+ },
104
+ },
94
105
  ],
95
106
  }),
96
107
  ],
@@ -98,6 +109,107 @@ import { TnestModule } from '@jdevel/tnest';
98
109
  export class AppModule {}
99
110
  ```
100
111
 
112
+ #### Async configuration with `forRootAsync`
113
+
114
+ Use `forRootAsync` when transport configuration comes from `ConfigService`, environment variables, or another async source.
115
+
116
+ **With `useFactory`:**
117
+
118
+ ```ts
119
+ import { Module } from '@nestjs/common';
120
+ import { ConfigModule, ConfigService } from '@nestjs/config';
121
+ import { Transport } from '@nestjs/microservices';
122
+ import { TnestModule } from '@jdevel/tnest';
123
+
124
+ @Module({
125
+ imports: [
126
+ TnestModule.forRootAsync({
127
+ imports: [ConfigModule],
128
+ // Declare client names upfront so they can be injected before the factory resolves
129
+ clientNames: ['USER_SERVICE', 'NOTIFICATION_SERVICE'],
130
+ useFactory: (config: ConfigService) => ({
131
+ clients: [
132
+ {
133
+ name: 'USER_SERVICE',
134
+ options: {
135
+ transport: Transport.TCP,
136
+ options: {
137
+ host: config.get('USER_SERVICE_HOST'),
138
+ port: config.get<number>('USER_SERVICE_PORT'),
139
+ },
140
+ },
141
+ },
142
+ {
143
+ name: 'NOTIFICATION_SERVICE',
144
+ options: {
145
+ transport: Transport.REDIS,
146
+ options: {
147
+ host: config.get('REDIS_HOST'),
148
+ port: config.get<number>('REDIS_PORT'),
149
+ },
150
+ },
151
+ },
152
+ ],
153
+ }),
154
+ inject: [ConfigService],
155
+ }),
156
+ ],
157
+ })
158
+ export class AppModule {}
159
+ ```
160
+
161
+ **With `useClass`:**
162
+
163
+ ```ts
164
+ import { Injectable } from '@nestjs/common';
165
+ import { Transport } from '@nestjs/microservices';
166
+ import { TnestModule, type TnestOptionsFactory, type TnestModuleOptions } from '@jdevel/tnest';
167
+
168
+ @Injectable()
169
+ class TnestConfigService implements TnestOptionsFactory {
170
+ createTnestOptions(): TnestModuleOptions {
171
+ return {
172
+ clients: [
173
+ {
174
+ name: 'USER_SERVICE',
175
+ options: {
176
+ transport: Transport.TCP,
177
+ options: { host: 'localhost', port: 3001 },
178
+ },
179
+ },
180
+ ],
181
+ };
182
+ }
183
+ }
184
+
185
+ @Module({
186
+ imports: [
187
+ TnestModule.forRootAsync({
188
+ clientNames: ['USER_SERVICE'],
189
+ useClass: TnestConfigService,
190
+ }),
191
+ ],
192
+ })
193
+ export class AppModule {}
194
+ ```
195
+
196
+ **With `useExisting`:**
197
+
198
+ ```ts
199
+ @Module({
200
+ imports: [
201
+ TnestModule.forRootAsync({
202
+ clientNames: ['USER_SERVICE'],
203
+ useExisting: TnestConfigService, // reuse an already-registered provider
204
+ imports: [ConfigModule],
205
+ }),
206
+ ],
207
+ })
208
+ export class AppModule {}
209
+ ```
210
+
211
+ > **Note:** The `clientNames` array tells the module which client tokens to register upfront. Each name must match a `name` in the `clients` array returned by the factory. Without `clientNames`, async clients won't be injectable by token.
212
+
101
213
  ### 3. Send messages (producer)
102
214
 
103
215
  ```ts
@@ -120,10 +232,10 @@ export class OrderService {
120
232
  }
121
233
 
122
234
  async createOrder(userId: string) {
123
- const user = await firstValueFrom(
124
- this.users.send('user.get', { id: userId }),
125
- );
235
+ // send() for commands and queries — type-safe pattern, payload, and response
236
+ const user = await firstValueFrom(this.users.send('user.get', { id: userId }));
126
237
 
238
+ // emit() for events — type-safe pattern and payload, no response
127
239
  this.users.emit('user.created', {
128
240
  userId: user.id,
129
241
  email: user.email,
@@ -147,8 +259,28 @@ this.users.send('user.created', { userId: '1', email: 'a@b.com' });
147
259
  // ~~~~~~~~~~~~~~ ERROR: 'user.created' is an event, use emit()
148
260
  ```
149
261
 
262
+ When using the second type parameter on handler decorators, the compiler also catches handler signature mismatches:
263
+
264
+ ```ts
265
+ @TypedMessagePattern<UserContracts, 'user.create'>('user.create')
266
+ async create(payload: { wrong: number }) {
267
+ // ~~~~~~~~~~~~~~~~ ERROR: expects { email: string; name: string }
268
+ return { id: '1' };
269
+ }
270
+
271
+ @TypedMessagePattern<UserContracts, 'user.get'>('user.get')
272
+ async get(payload: { id: string }): Promise<{ wrong: boolean }> {
273
+ // ~~~~~~~~~~~~~~~~~ ERROR: must return User
274
+ return { wrong: true };
275
+ }
276
+ ```
277
+
278
+ > **Note:** Omitting the second type parameter preserves the existing behavior — the decorator validates the pattern string but does not constrain the method signature.
279
+
150
280
  ### 4. Handle messages (consumer)
151
281
 
282
+ #### Using decorators
283
+
152
284
  ```ts
153
285
  // user.controller.ts
154
286
  import { Controller } from '@nestjs/common';
@@ -157,25 +289,56 @@ import type { UserContracts } from './contracts/user.contracts';
157
289
 
158
290
  @Controller()
159
291
  export class UserController {
160
- @TypedMessagePattern<UserContracts>('user.create')
292
+ // Pass the pattern as a second type parameter to enforce the method signature.
293
+ // The compiler will error if the payload or return type doesn't match the contract.
294
+ @TypedMessagePattern<UserContracts, 'user.create'>('user.create')
161
295
  async create(payload: { email: string; name: string }) {
162
296
  return { id: crypto.randomUUID(), ...payload };
163
297
  }
164
298
 
165
- @TypedMessagePattern<UserContracts>('user.get')
299
+ @TypedMessagePattern<UserContracts, 'user.get'>('user.get')
166
300
  async get(payload: { id: string }) {
167
301
  return { id: payload.id, email: 'user@example.com', name: 'Example' };
168
302
  }
169
303
 
170
- @TypedEventPattern<UserContracts>('user.created')
304
+ @TypedEventPattern<UserContracts, 'user.created'>('user.created')
171
305
  async handleCreated(payload: { userId: string; email: string }) {
172
306
  console.log(`User created: ${payload.userId}`);
173
307
  }
174
308
  }
175
309
  ```
176
310
 
311
+ #### Using handler type helpers
312
+
313
+ If you prefer explicit typing without decorators, use `TypedMessageHandler` and `TypedEventHandler`:
314
+
315
+ ```ts
316
+ import type { TypedMessageHandler, TypedEventHandler } from '@jdevel/tnest';
317
+ import { MessagePattern, EventPattern } from '@nestjs/microservices';
318
+ import type { UserContracts } from './contracts/user.contracts';
319
+
320
+ @Controller()
321
+ export class UserController {
322
+ @MessagePattern('user.create')
323
+ create: TypedMessageHandler<UserContracts, 'user.create'> = async (payload) => {
324
+ // payload is typed as { email: string; name: string }
325
+ // return type is enforced as User | Promise<User> | Observable<User>
326
+ return { id: crypto.randomUUID(), ...payload };
327
+ };
328
+
329
+ @EventPattern('user.created')
330
+ handleCreated: TypedEventHandler<UserContracts, 'user.created'> = async (payload) => {
331
+ // payload is typed as { userId: string; email: string }
332
+ // return type is enforced as void | Promise<void>
333
+ console.log(`User created: ${payload.userId}`);
334
+ };
335
+ }
336
+ ```
337
+
177
338
  ## Testing
178
339
 
340
+ ### MockTypedClient
341
+
179
342
  `MockTypedClient` records every message and returns configurable canned responses:
180
343
 
181
344
  ```ts
@@ -185,66 +348,45 @@ import type { UserContracts } from './contracts/user.contracts';
185
348
 
186
349
  const mock = new MockTypedClient<UserContracts>();
187
350
 
351
+ // Set up canned responses
188
352
  mock.setResponse('user.get', {
189
353
  id: '42',
190
354
  email: 'test@example.com',
191
355
  name: 'Test User',
192
356
  });
193
357
 
358
+ // Use the mock exactly like a real TypedClient
194
359
  const user = await firstValueFrom(mock.send('user.get', { id: '42' }));
195
360
  // user.id === '42'
196
361
 
197
- expect(mock.messages).toEqual([
198
- { type: 'send', pattern: 'user.get', payload: { id: '42' } },
199
- ]);
362
+ // Assert on recorded messages
363
+ expect(mock.messages).toEqual([{ type: 'send', pattern: 'user.get', payload: { id: '42' } }]);
364
+
365
+ // Reset between tests
366
+ mock.reset();
200
367
  ```
201
368
 
369
+ ### TestContractModule
370
+
202
371
  For integration tests, `TestContractModule` swaps real clients for mocks:
203
372
 
204
373
  ```ts
205
374
  import { Test } from '@nestjs/testing';
206
- import { MockTypedClient, TestContractModule } from '@jdevel/tnest';
375
+ import { MockTypedClient, TestContractModule, getClientToken } from '@jdevel/tnest';
207
376
  import type { UserContracts } from './contracts/user.contracts';
208
377
 
209
- const mock = new MockTypedClient<UserContracts>();
378
+ const mockUserClient = new MockTypedClient<UserContracts>();
210
379
 
211
380
  const module = await Test.createTestingModule({
212
- imports: [
213
- TestContractModule.register([
214
- { name: 'USER_SERVICE', mock },
215
- ]),
216
- ],
381
+ imports: [TestContractModule.register([{ name: 'USER_SERVICE', mock: mockUserClient }])],
217
382
  providers: [OrderService],
218
383
  }).compile();
219
- ```
220
-
221
- ## Async Module Configuration
222
384
 
223
- Use `forRootAsync` when transport config comes from `ConfigService` or another async source:
385
+ const service = module.get(OrderService);
224
386
 
225
- ```ts
226
- import { ConfigModule, ConfigService } from '@nestjs/config';
227
- import { TnestModule } from '@jdevel/tnest';
228
- import { Transport } from '@nestjs/microservices';
229
-
230
- TnestModule.forRootAsync({
231
- imports: [ConfigModule],
232
- useFactory: (config: ConfigService) => ({
233
- clients: [
234
- {
235
- name: 'USER_SERVICE',
236
- options: {
237
- transport: Transport.TCP,
238
- options: {
239
- host: config.get('USER_SERVICE_HOST'),
240
- port: config.get('USER_SERVICE_PORT'),
241
- },
242
- },
243
- },
244
- ],
245
- }),
246
- inject: [ConfigService],
247
- });
387
+ // The mock is also retrievable by token
388
+ const client = module.get(getClientToken('USER_SERVICE'));
389
+ // client === mockUserClient
248
390
  ```
249
391
 
250
392
  ## Runtime Validation (Optional)
@@ -261,17 +403,18 @@ import {
261
403
  } from '@jdevel/tnest';
262
404
 
263
405
  @Injectable()
264
- class MyValidator implements ContractValidator {
406
+ class ZodValidator implements ContractValidator {
265
407
  validate(payload: unknown): void {
266
408
  // your validation logic (zod, class-validator, joi, etc.)
409
+ // throw an error if validation fails
267
410
  }
268
411
  }
269
412
 
270
413
  // Register in module providers
271
- { provide: CONTRACT_VALIDATOR, useClass: MyValidator }
414
+ { provide: CONTRACT_VALIDATOR, useClass: ZodValidator }
272
415
 
273
- // Apply to handlers
274
- @TypedMessagePattern<UserContracts>('user.create')
416
+ // Apply to handlers — validation runs before the handler method
417
+ @TypedMessagePattern<UserContracts, 'user.create'>('user.create')
275
418
  @ValidateContract()
276
419
  async create(payload: CreateUserDto) {
277
420
  // payload has been validated at runtime
@@ -283,6 +426,7 @@ async create(payload: CreateUserDto) {
283
426
  Provide custom payload serialization (protobuf, msgpack, etc.) by implementing `PayloadSerializer` and `PayloadDeserializer`:
284
427
 
285
428
  ```ts
429
+ import { Injectable } from '@nestjs/common';
286
430
  import {
287
431
  PAYLOAD_SERIALIZER,
288
432
  PAYLOAD_DESERIALIZER,
@@ -292,8 +436,12 @@ import {
292
436
 
293
437
  @Injectable()
294
438
  class MsgpackSerializer implements PayloadSerializer, PayloadDeserializer {
295
- serialize(payload: unknown) { return msgpack.encode(payload); }
296
- deserialize(data: unknown) { return msgpack.decode(data as Buffer); }
439
+ serialize(payload: unknown) {
440
+ return msgpack.encode(payload);
441
+ }
442
+ deserialize(data: unknown) {
443
+ return msgpack.decode(data as Buffer);
444
+ }
297
445
  }
298
446
 
299
447
  // Register in module providers
@@ -301,12 +449,15 @@ class MsgpackSerializer implements PayloadSerializer, PayloadDeserializer {
301
449
  { provide: PAYLOAD_DESERIALIZER, useClass: MsgpackSerializer }
302
450
  ```
303
451
 
452
+ A `DefaultPayloadSerializer` (pass-through) is included and used when no custom serializer is registered.
453
+
304
454
  ## Utility Types
305
455
 
306
456
  Extract type information from your contract registry:
307
457
 
308
458
  ```ts
309
459
  import type {
460
+ PatternOf,
310
461
  PayloadOf,
311
462
  ResponseOf,
312
463
  CommandPatterns,
@@ -316,42 +467,65 @@ import type {
316
467
  CommandsOf,
317
468
  EventsOf,
318
469
  QueriesOf,
470
+ ValidateRegistry,
319
471
  } from '@jdevel/tnest';
320
472
 
321
- type Payload = PayloadOf<UserContracts['user.create']>; // CreateUserDto
322
- type Response = ResponseOf<UserContracts['user.create']>; // User
323
- type Cmds = CommandPatterns<UserContracts>; // 'user.create'
324
- type Evts = EventPatterns<UserContracts>; // 'user.created'
325
- type Qrys = QueryPatterns<UserContracts>; // 'user.get'
326
- type Sendable = SendablePatterns<UserContracts>; // 'user.create' | 'user.get'
473
+ // Extract parts of a single contract
474
+ type CreatePayload = PayloadOf<UserContracts['user.create']>; // CreateUserDto
475
+ type CreateResponse = ResponseOf<UserContracts['user.create']>; // User
476
+ type CreatePattern = PatternOf<UserContracts['user.create']>; // 'user.create'
477
+
478
+ // Pattern string unions by contract kind
479
+ type Cmds = CommandPatterns<UserContracts>; // 'user.create'
480
+ type Evts = EventPatterns<UserContracts>; // 'user.created'
481
+ type Qrys = QueryPatterns<UserContracts>; // 'user.get'
482
+ type Sendable = SendablePatterns<UserContracts>; // 'user.create' | 'user.get'
483
+
484
+ // Filter a registry to contracts of a specific kind
485
+ type OnlyCommands = CommandsOf<UserContracts>; // { 'user.create': Command<...> }
486
+ type OnlyEvents = EventsOf<UserContracts>; // { 'user.created': Event<...> }
487
+ type OnlyQueries = QueriesOf<UserContracts>; // { 'user.get': Query<...> }
488
+
489
+ // Validate that a registry's keys match its pattern type parameters
490
+ type Validated = ValidateRegistry<UserContracts>;
491
+ // Produces an error type if any key doesn't match its contract's pattern
327
492
  ```
328
493
 
329
494
  ## API Reference
330
495
 
331
- | Export | Kind | Description |
332
- |--------|------|-------------|
333
- | `TnestModule` | Module | Dynamic module with `forRoot` / `forRootAsync` |
334
- | `TypedClient` | Class | Type-safe wrapper around `ClientProxy` |
335
- | `TypedClientFactory` | Service | Creates `TypedClient` instances |
336
- | `TypedMessagePattern` | Decorator | Typed `@MessagePattern` for commands/queries |
337
- | `TypedEventPattern` | Decorator | Typed `@EventPattern` for events |
338
- | `MockTypedClient` | Class | Test double that records messages |
339
- | `TestContractModule` | Module | Registers mock clients for testing |
340
- | `ValidateContract` | Decorator | Opt-in runtime payload validation |
341
- | `getClientToken` | Function | Returns injection token for a named client |
342
- | `defineRegistry` | Function | Builder helper for defining contract registries |
343
- | `command` / `event` / `query` | Functions | Builder helpers for individual contracts |
344
- | `Command` / `Event` / `Query` | Type | Contract type interfaces |
345
- | `ContractRegistry` | Type | Base type for a registry of contracts |
346
- | `PayloadOf` / `ResponseOf` / `PatternOf` | Type | Extract parts of a contract |
347
- | `CommandsOf` / `EventsOf` / `QueriesOf` | Type | Filter registry by contract kind |
348
- | `CommandPatterns` / `EventPatterns` / `QueryPatterns` | Type | Pattern string unions by kind |
349
- | `SendablePatterns` | Type | Union of command + query patterns |
350
- | `ContractValidator` | Interface | Implement for runtime validation |
351
- | `PayloadSerializer` / `PayloadDeserializer` | Interface | Implement for custom serialization |
352
- | `CONTRACT_VALIDATOR` | Token | Injection token for validator |
353
- | `PAYLOAD_SERIALIZER` / `PAYLOAD_DESERIALIZER` | Token | Injection tokens for serialization |
496
+ | Export | Kind | Description |
497
+ | ----------------------------------------------------- | --------- | --------------------------------------------------- |
498
+ | `TnestModule` | Module | Dynamic module with `forRoot` / `forRootAsync` |
499
+ | `TypedClient` | Class | Type-safe wrapper around `ClientProxy` |
500
+ | `TypedClientFactory` | Service | Creates `TypedClient` instances |
501
+ | `TypedMessagePattern` | Decorator | Typed `@MessagePattern` for commands/queries |
502
+ | `TypedEventPattern` | Decorator | Typed `@EventPattern` for events |
503
+ | `ValidateContract` | Decorator | Opt-in runtime payload validation |
504
+ | `MockTypedClient` | Class | Test double that records messages |
505
+ | `TestContractModule` | Module | Registers mock clients for testing |
506
+ | `getClientToken` | Function | Returns injection token for a named client |
507
+ | `defineRegistry` | Function | Builder helper for defining contract registries |
508
+ | `command` / `event` / `query` | Functions | Builder helpers for individual contracts |
509
+ | `Command` / `Event` / `Query` | Type | Contract type interfaces |
510
+ | `ContractRegistry` | Type | Base type for a registry of contracts |
511
+ | `ValidateRegistry` | Type | Validates registry keys match contract patterns |
512
+ | `PayloadOf` / `ResponseOf` / `PatternOf` | Type | Extract parts of a contract |
513
+ | `CommandsOf` / `EventsOf` / `QueriesOf` | Type | Filter registry by contract kind |
514
+ | `CommandPatterns` / `EventPatterns` / `QueryPatterns` | Type | Pattern string unions by kind |
515
+ | `SendablePatterns` | Type | Union of command + query patterns |
516
+ | `TypedMessageHandler` | Type | Function signature for command/query handlers |
517
+ | `TypedEventHandler` | Type | Function signature for event handlers |
518
+ | `TnestModuleOptions` | Interface | Configuration for `forRoot` |
519
+ | `TnestModuleAsyncOptions` | Interface | Configuration for `forRootAsync` |
520
+ | `TnestOptionsFactory` | Interface | Implement for `useClass`/`useExisting` async config |
521
+ | `TnestClientDefinition` | Interface | Client name + transport options pair |
522
+ | `ContractValidator` | Interface | Implement for runtime validation |
523
+ | `PayloadSerializer` / `PayloadDeserializer` | Interface | Implement for custom serialization |
524
+ | `DefaultPayloadSerializer` | Class | Pass-through serializer (default) |
525
+ | `CONTRACT_VALIDATOR` | Token | Injection token for validator |
526
+ | `PAYLOAD_SERIALIZER` / `PAYLOAD_DESERIALIZER` | Token | Injection tokens for serialization |
527
+ | `TNEST_OPTIONS` | Token | Injection token for module options |
354
528
 
355
529
  ## License
356
530
 
357
- MIT
531
+ MIT
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const constants_1 = require("../constants");
4
+ describe('TNEST_OPTIONS', () => {
5
+ it('is a Symbol', () => {
6
+ expect(typeof constants_1.TNEST_OPTIONS).toBe('symbol');
7
+ });
8
+ });
9
+ describe('getClientToken()', () => {
10
+ it('returns a string prefixed with TNEST_CLIENT_PREFIX', () => {
11
+ const token = (0, constants_1.getClientToken)('USER_SERVICE');
12
+ expect(token).toBe(`${constants_1.TNEST_CLIENT_PREFIX}USER_SERVICE`);
13
+ });
14
+ it('returns deterministic tokens for the same name', () => {
15
+ expect((0, constants_1.getClientToken)('SVC')).toBe((0, constants_1.getClientToken)('SVC'));
16
+ });
17
+ it('returns different tokens for different names', () => {
18
+ expect((0, constants_1.getClientToken)('A')).not.toBe((0, constants_1.getClientToken)('B'));
19
+ });
20
+ });
21
+ //# sourceMappingURL=constants.spec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.spec.js","sourceRoot":"","sources":["../../src/__tests__/constants.spec.ts"],"names":[],"mappings":";;AAAA,4CAAkF;AAElF,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;IAC7B,EAAE,CAAC,aAAa,EAAE,GAAG,EAAE;QACrB,MAAM,CAAC,OAAO,yBAAa,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC9C,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,kBAAkB,EAAE,GAAG,EAAE;IAChC,EAAE,CAAC,oDAAoD,EAAE,GAAG,EAAE;QAC5D,MAAM,KAAK,GAAG,IAAA,0BAAc,EAAC,cAAc,CAAC,CAAC;QAC7C,MAAM,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,GAAG,+BAAmB,cAAc,CAAC,CAAC;IAC3D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,gDAAgD,EAAE,GAAG,EAAE;QACxD,MAAM,CAAC,IAAA,0BAAc,EAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,IAAA,0BAAc,EAAC,KAAK,CAAC,CAAC,CAAC;IAC5D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,8CAA8C,EAAE,GAAG,EAAE;QACtD,MAAM,CAAC,IAAA,0BAAc,EAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,IAAA,0BAAc,EAAC,GAAG,CAAC,CAAC,CAAC;IAC5D,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,88 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const testing_1 = require("@nestjs/testing");
4
+ const tnest_module_1 = require("../tnest.module");
5
+ const client_1 = require("../client");
6
+ const constants_1 = require("../constants");
7
+ describe('TnestModule', () => {
8
+ describe('forRoot()', () => {
9
+ it('provides TypedClientFactory', async () => {
10
+ const module = await testing_1.Test.createTestingModule({
11
+ imports: [tnest_module_1.TnestModule.forRoot()],
12
+ }).compile();
13
+ const factory = module.get(client_1.TypedClientFactory);
14
+ expect(factory).toBeInstanceOf(client_1.TypedClientFactory);
15
+ });
16
+ it('provides TNEST_OPTIONS', async () => {
17
+ const options = { clients: [] };
18
+ const module = await testing_1.Test.createTestingModule({
19
+ imports: [tnest_module_1.TnestModule.forRoot(options)],
20
+ }).compile();
21
+ const resolved = module.get(constants_1.TNEST_OPTIONS);
22
+ expect(resolved).toBe(options);
23
+ });
24
+ it('registers named client tokens from client definitions', async () => {
25
+ const module = await testing_1.Test.createTestingModule({
26
+ imports: [
27
+ tnest_module_1.TnestModule.forRoot({
28
+ clients: [{ name: 'USER_SERVICE', options: { transport: 0 } }],
29
+ }),
30
+ ],
31
+ }).compile();
32
+ const token = (0, constants_1.getClientToken)('USER_SERVICE');
33
+ const client = module.get(token);
34
+ expect(client).toBeDefined();
35
+ });
36
+ });
37
+ describe('forRootAsync()', () => {
38
+ it('provides TypedClientFactory', async () => {
39
+ const module = await testing_1.Test.createTestingModule({
40
+ imports: [
41
+ tnest_module_1.TnestModule.forRootAsync({
42
+ useFactory: () => ({ clients: [] }),
43
+ }),
44
+ ],
45
+ }).compile();
46
+ const factory = module.get(client_1.TypedClientFactory);
47
+ expect(factory).toBeInstanceOf(client_1.TypedClientFactory);
48
+ });
49
+ it('resolves TNEST_OPTIONS from factory', async () => {
50
+ const options = { clients: [] };
51
+ const module = await testing_1.Test.createTestingModule({
52
+ imports: [
53
+ tnest_module_1.TnestModule.forRootAsync({
54
+ useFactory: () => options,
55
+ }),
56
+ ],
57
+ }).compile();
58
+ const resolved = module.get(constants_1.TNEST_OPTIONS);
59
+ expect(resolved).toBe(options);
60
+ });
61
+ it('registers named client tokens when clientNames are provided', async () => {
62
+ const module = await testing_1.Test.createTestingModule({
63
+ imports: [
64
+ tnest_module_1.TnestModule.forRootAsync({
65
+ clientNames: ['USER_SERVICE'],
66
+ useFactory: () => ({
67
+ clients: [{ name: 'USER_SERVICE', options: { transport: 0 } }],
68
+ }),
69
+ }),
70
+ ],
71
+ }).compile();
72
+ const token = (0, constants_1.getClientToken)('USER_SERVICE');
73
+ const client = module.get(token);
74
+ expect(client).toBeDefined();
75
+ });
76
+ it('throws when clientName is not found in resolved options', async () => {
77
+ await expect(testing_1.Test.createTestingModule({
78
+ imports: [
79
+ tnest_module_1.TnestModule.forRootAsync({
80
+ clientNames: ['MISSING_SERVICE'],
81
+ useFactory: () => ({ clients: [] }),
82
+ }),
83
+ ],
84
+ }).compile()).rejects.toThrow(/client "MISSING_SERVICE" was declared in clientNames but not found/);
85
+ });
86
+ });
87
+ });
88
+ //# sourceMappingURL=tnest-module.spec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tnest-module.spec.js","sourceRoot":"","sources":["../../src/__tests__/tnest-module.spec.ts"],"names":[],"mappings":"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"}