@dismissible/nestjs-dismissible 0.0.2-canary.c91edbc.0 → 0.0.2-canary.d2f56d7.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.
Files changed (41) hide show
  1. package/README.md +51 -67
  2. package/package.json +3 -3
  3. package/src/api/dismissible-item-response.dto.ts +0 -8
  4. package/src/api/dismissible-item.mapper.spec.ts +0 -12
  5. package/src/api/dismissible-item.mapper.ts +2 -8
  6. package/src/api/index.ts +3 -0
  7. package/src/api/use-cases/dismiss/dismiss.controller.spec.ts +1 -2
  8. package/src/api/use-cases/dismiss/dismiss.controller.ts +8 -8
  9. package/src/api/use-cases/get-or-create/get-or-create.controller.spec.ts +2 -42
  10. package/src/api/use-cases/get-or-create/get-or-create.controller.ts +10 -56
  11. package/src/api/use-cases/get-or-create/index.ts +0 -1
  12. package/src/api/use-cases/restore/restore.controller.spec.ts +1 -2
  13. package/src/api/use-cases/restore/restore.controller.ts +8 -8
  14. package/src/api/validation/index.ts +2 -0
  15. package/src/api/validation/param-validation.pipe.spec.ts +317 -0
  16. package/src/api/validation/param-validation.pipe.ts +42 -0
  17. package/src/api/validation/param.decorators.ts +32 -0
  18. package/src/core/dismissible-core.service.spec.ts +3 -45
  19. package/src/core/dismissible-core.service.ts +10 -27
  20. package/src/core/dismissible.service.spec.ts +23 -16
  21. package/src/core/dismissible.service.ts +28 -11
  22. package/src/core/hook-runner.service.spec.ts +369 -19
  23. package/src/core/hook-runner.service.ts +17 -17
  24. package/src/core/index.ts +0 -1
  25. package/src/core/lifecycle-hook.interface.ts +8 -8
  26. package/src/core/service-responses.interface.ts +9 -9
  27. package/src/dismissible.module.integration.spec.ts +685 -0
  28. package/src/dismissible.module.ts +6 -10
  29. package/src/events/dismissible.events.ts +16 -39
  30. package/src/index.ts +1 -0
  31. package/src/request/request-context.decorator.ts +1 -0
  32. package/src/request/request-context.interface.ts +6 -0
  33. package/src/response/http-exception-filter.spec.ts +213 -0
  34. package/src/testing/factories.ts +5 -8
  35. package/src/utils/dismissible.helper.ts +2 -2
  36. package/src/validation/dismissible-input.dto.ts +47 -0
  37. package/src/validation/index.ts +1 -0
  38. package/tsconfig.json +3 -0
  39. package/tsconfig.spec.json +12 -0
  40. package/src/api/use-cases/get-or-create/get-or-create.request.dto.ts +0 -17
  41. package/src/core/create-options.ts +0 -9
@@ -4,16 +4,17 @@ import { DismissibleService } from './dismissible.service';
4
4
  import { DismissibleCoreService } from './dismissible-core.service';
5
5
  import { HookRunner, IHookRunResult } from './hook-runner.service';
6
6
  import { IDismissibleLogger } from '@dismissible/nestjs-logger';
7
+ import { ValidationService } from '@dismissible/nestjs-validation';
7
8
  import { DismissibleEvents } from '../events';
8
- import { BaseMetadata } from '@dismissible/nestjs-dismissible-item';
9
9
  import { createTestItem, createTestContext } from '../testing/factories';
10
10
 
11
11
  describe('DismissibleService', () => {
12
- let service: DismissibleService<BaseMetadata>;
13
- let mockCoreService: jest.Mocked<DismissibleCoreService<BaseMetadata>>;
14
- let mockHookRunner: jest.Mocked<HookRunner<BaseMetadata>>;
12
+ let service: DismissibleService;
13
+ let mockCoreService: jest.Mocked<DismissibleCoreService>;
14
+ let mockHookRunner: jest.Mocked<HookRunner>;
15
15
  let mockEventEmitter: jest.Mocked<EventEmitter2>;
16
16
  let mockLogger: jest.Mocked<IDismissibleLogger>;
17
+ let mockValidationService: jest.Mocked<ValidationService>;
17
18
 
18
19
  const testUserId = 'test-user-id';
19
20
 
@@ -29,8 +30,18 @@ describe('DismissibleService', () => {
29
30
  mockHookRunner = mock(HookRunner, { failIfMockNotProvided: false });
30
31
  mockEventEmitter = mock(EventEmitter2, { failIfMockNotProvided: false });
31
32
  mockLogger = mock<IDismissibleLogger>({ failIfMockNotProvided: false });
32
-
33
- service = new DismissibleService(mockCoreService, mockHookRunner, mockEventEmitter, mockLogger);
33
+ mockValidationService = mock(ValidationService, { failIfMockNotProvided: false });
34
+
35
+ // Mock validateDto to resolve successfully by default
36
+ mockValidationService.validateDto.mockResolvedValue({} as never);
37
+
38
+ service = new DismissibleService(
39
+ mockCoreService,
40
+ mockHookRunner,
41
+ mockEventEmitter,
42
+ mockLogger,
43
+ mockValidationService,
44
+ );
34
45
  });
35
46
 
36
47
  describe('getOrCreate', () => {
@@ -42,7 +53,7 @@ describe('DismissibleService', () => {
42
53
  mockHookRunner.runPreGet.mockResolvedValue(createHookResult('existing-item'));
43
54
  mockCoreService.get.mockResolvedValue(item);
44
55
 
45
- const result = await service.getOrCreate('existing-item', testUserId, undefined, context);
56
+ const result = await service.getOrCreate('existing-item', testUserId, context);
46
57
 
47
58
  expect(mockHookRunner.runPreRequest).toHaveBeenCalled();
48
59
  expect(mockCoreService.get).toHaveBeenCalledWith('existing-item', testUserId);
@@ -79,13 +90,13 @@ describe('DismissibleService', () => {
79
90
  return item;
80
91
  });
81
92
 
82
- const result = await service.getOrCreate('new-item', testUserId, undefined, context);
93
+ const result = await service.getOrCreate('new-item', testUserId, context);
83
94
 
84
95
  // Verify pre-create hooks run BEFORE create
85
96
  expect(callOrder).toEqual(['runPreCreate', 'create']);
86
97
  expect(mockHookRunner.runPreRequest).toHaveBeenCalled();
87
98
  expect(mockHookRunner.runPreCreate).toHaveBeenCalled();
88
- expect(mockCoreService.create).toHaveBeenCalledWith('new-item', testUserId, undefined);
99
+ expect(mockCoreService.create).toHaveBeenCalledWith('new-item', testUserId);
89
100
  expect(mockHookRunner.runPostCreate).toHaveBeenCalled();
90
101
  expect(mockHookRunner.runPostRequest).toHaveBeenCalled();
91
102
  expect(mockEventEmitter.emit).toHaveBeenCalledWith(
@@ -108,9 +119,7 @@ describe('DismissibleService', () => {
108
119
  context: createTestContext(),
109
120
  });
110
121
 
111
- await expect(
112
- service.getOrCreate('new-item', testUserId, undefined, context),
113
- ).rejects.toThrow();
122
+ await expect(service.getOrCreate('new-item', testUserId, context)).rejects.toThrow();
114
123
 
115
124
  // Verify create was NOT called because pre-create hook blocked it
116
125
  expect(mockCoreService.create).not.toHaveBeenCalled();
@@ -135,9 +144,7 @@ describe('DismissibleService', () => {
135
144
  context: createTestContext(),
136
145
  });
137
146
 
138
- await expect(
139
- service.getOrCreate('existing-item', testUserId, undefined, context),
140
- ).rejects.toThrow();
147
+ await expect(service.getOrCreate('existing-item', testUserId, context)).rejects.toThrow();
141
148
 
142
149
  // Verify post-get hooks were NOT called because pre-get hook blocked
143
150
  expect(mockHookRunner.runPostGet).not.toHaveBeenCalled();
@@ -208,7 +215,7 @@ describe('DismissibleService', () => {
208
215
  mockHookRunner.runPreGet.mockResolvedValue(createHookResult('test-item'));
209
216
  mockCoreService.get.mockResolvedValue(item);
210
217
 
211
- await service.getOrCreate('test-item', testUserId, undefined, context);
218
+ await service.getOrCreate('test-item', testUserId, context);
212
219
 
213
220
  expect(mockLogger.debug).toHaveBeenCalledWith(
214
221
  expect.stringContaining('getOrCreate called'),
@@ -8,8 +8,6 @@ import {
8
8
  IDismissServiceResponse,
9
9
  IRestoreServiceResponse,
10
10
  } from './service-responses.interface';
11
- import { BaseMetadata } from '@dismissible/nestjs-dismissible-item';
12
- import { ICreateItemOptions } from './create-options';
13
11
  import { IRequestContext } from '../request/request-context.interface';
14
12
  import { DismissibleEvents } from '../events';
15
13
  import {
@@ -18,36 +16,49 @@ import {
18
16
  ItemDismissedEvent,
19
17
  ItemRestoredEvent,
20
18
  } from '../events';
19
+ import { ValidationService } from '@dismissible/nestjs-validation';
20
+ import { DismissibleInputDto } from '../validation';
21
21
 
22
22
  /**
23
23
  * Main orchestration service for dismissible operations.
24
24
  * Coordinates core logic, hooks, and events.
25
25
  */
26
26
  @Injectable()
27
- export class DismissibleService<TMetadata extends BaseMetadata = BaseMetadata> {
27
+ export class DismissibleService {
28
28
  constructor(
29
- private readonly coreService: DismissibleCoreService<TMetadata>,
30
- private readonly hookRunner: HookRunner<TMetadata>,
29
+ private readonly coreService: DismissibleCoreService,
30
+ private readonly hookRunner: HookRunner,
31
31
  private readonly eventEmitter: EventEmitter2,
32
32
  @Inject(DISMISSIBLE_LOGGER)
33
33
  private readonly logger: IDismissibleLogger,
34
+ private readonly validationService: ValidationService,
34
35
  ) {}
35
36
 
37
+ /**
38
+ * Validates input parameters for all service methods.
39
+ * Provides defense in depth when the service is used directly without controllers.
40
+ */
41
+ private async validateInput(itemId: string, userId: string): Promise<void> {
42
+ // Validate itemId and userId
43
+ await this.validationService.validateDto(DismissibleInputDto, { itemId, userId });
44
+ }
45
+
36
46
  /**
37
47
  * Get an existing item or create a new one.
38
48
  * @param itemId The item identifier
39
49
  * @param userId The user identifier (required)
40
- * @param options Optional creation options (metadata)
41
50
  * @param context Optional request context for tracing
42
51
  */
43
52
  async getOrCreate(
44
53
  itemId: string,
45
54
  userId: string,
46
- options?: ICreateItemOptions<TMetadata>,
47
55
  context?: IRequestContext,
48
- ): Promise<IGetOrCreateServiceResponse<TMetadata>> {
56
+ ): Promise<IGetOrCreateServiceResponse> {
49
57
  this.logger.debug(`getOrCreate called`, { itemId, userId });
50
58
 
59
+ // Validate input parameters
60
+ await this.validateInput(itemId, userId);
61
+
51
62
  // Run global pre-request hooks (auth, rate limiting, validation)
52
63
  const preResult = await this.hookRunner.runPreRequest(itemId, userId, context);
53
64
  HookRunner.throwIfBlocked(preResult);
@@ -103,7 +114,7 @@ export class DismissibleService<TMetadata extends BaseMetadata = BaseMetadata> {
103
114
  HookRunner.throwIfBlocked(preCreateResult);
104
115
 
105
116
  // Now create the item
106
- const createdItem = await this.coreService.create(resolvedId, resolvedUserId, options);
117
+ const createdItem = await this.coreService.create(resolvedId, resolvedUserId);
107
118
 
108
119
  // Run post-create hooks
109
120
  await this.hookRunner.runPostCreate(resolvedId, createdItem, resolvedUserId, resolvedContext);
@@ -135,9 +146,12 @@ export class DismissibleService<TMetadata extends BaseMetadata = BaseMetadata> {
135
146
  itemId: string,
136
147
  userId: string,
137
148
  context?: IRequestContext,
138
- ): Promise<IDismissServiceResponse<TMetadata>> {
149
+ ): Promise<IDismissServiceResponse> {
139
150
  this.logger.debug(`dismiss called`, { itemId, userId });
140
151
 
152
+ // Validate input parameters
153
+ await this.validateInput(itemId, userId);
154
+
141
155
  // Run global pre-request hooks (auth, rate limiting, validation)
142
156
  const preRequestResult = await this.hookRunner.runPreRequest(itemId, userId, context);
143
157
  HookRunner.throwIfBlocked(preRequestResult);
@@ -190,9 +204,12 @@ export class DismissibleService<TMetadata extends BaseMetadata = BaseMetadata> {
190
204
  itemId: string,
191
205
  userId: string,
192
206
  context?: IRequestContext,
193
- ): Promise<IRestoreServiceResponse<TMetadata>> {
207
+ ): Promise<IRestoreServiceResponse> {
194
208
  this.logger.debug(`restore called`, { itemId, userId });
195
209
 
210
+ // Validate input parameters
211
+ await this.validateInput(itemId, userId);
212
+
196
213
  // Run global pre-request hooks (auth, rate limiting, validation)
197
214
  const preRequestResult = await this.hookRunner.runPreRequest(itemId, userId, context);
198
215
  HookRunner.throwIfBlocked(preRequestResult);