@dismissible/nestjs-dismissible 0.0.2-canary.6b97aa7.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 (69) hide show
  1. package/README.md +490 -0
  2. package/jest.config.ts +29 -0
  3. package/package.json +66 -0
  4. package/project.json +42 -0
  5. package/src/api/dismissible-item-response.dto.ts +30 -0
  6. package/src/api/dismissible-item.mapper.spec.ts +51 -0
  7. package/src/api/dismissible-item.mapper.ts +27 -0
  8. package/src/api/index.ts +7 -0
  9. package/src/api/use-cases/api-tags.constants.ts +4 -0
  10. package/src/api/use-cases/dismiss/dismiss.controller.spec.ts +41 -0
  11. package/src/api/use-cases/dismiss/dismiss.controller.ts +63 -0
  12. package/src/api/use-cases/dismiss/dismiss.response.dto.ts +7 -0
  13. package/src/api/use-cases/dismiss/index.ts +2 -0
  14. package/src/api/use-cases/get-or-create/get-or-create.controller.spec.ts +36 -0
  15. package/src/api/use-cases/get-or-create/get-or-create.controller.ts +60 -0
  16. package/src/api/use-cases/get-or-create/get-or-create.response.dto.ts +7 -0
  17. package/src/api/use-cases/get-or-create/index.ts +2 -0
  18. package/src/api/use-cases/index.ts +3 -0
  19. package/src/api/use-cases/restore/index.ts +2 -0
  20. package/src/api/use-cases/restore/restore.controller.spec.ts +41 -0
  21. package/src/api/use-cases/restore/restore.controller.ts +63 -0
  22. package/src/api/use-cases/restore/restore.response.dto.ts +7 -0
  23. package/src/api/validation/index.ts +2 -0
  24. package/src/api/validation/param-validation.pipe.spec.ts +313 -0
  25. package/src/api/validation/param-validation.pipe.ts +38 -0
  26. package/src/api/validation/param.decorators.ts +32 -0
  27. package/src/core/dismissible-core.service.spec.ts +403 -0
  28. package/src/core/dismissible-core.service.ts +173 -0
  29. package/src/core/dismissible.service.spec.ts +226 -0
  30. package/src/core/dismissible.service.ts +227 -0
  31. package/src/core/hook-runner.service.spec.ts +736 -0
  32. package/src/core/hook-runner.service.ts +368 -0
  33. package/src/core/index.ts +5 -0
  34. package/src/core/lifecycle-hook.interface.ts +148 -0
  35. package/src/core/service-responses.interface.ts +34 -0
  36. package/src/dismissible.module.integration.spec.ts +687 -0
  37. package/src/dismissible.module.ts +79 -0
  38. package/src/events/dismissible.events.ts +82 -0
  39. package/src/events/events.constants.ts +21 -0
  40. package/src/events/index.ts +2 -0
  41. package/src/exceptions/dismissible.exceptions.spec.ts +50 -0
  42. package/src/exceptions/dismissible.exceptions.ts +69 -0
  43. package/src/exceptions/index.ts +1 -0
  44. package/src/index.ts +9 -0
  45. package/src/request/index.ts +2 -0
  46. package/src/request/request-context.decorator.ts +15 -0
  47. package/src/request/request-context.interface.ts +12 -0
  48. package/src/response/dtos/base-response.dto.ts +11 -0
  49. package/src/response/dtos/error-response.dto.ts +36 -0
  50. package/src/response/dtos/index.ts +3 -0
  51. package/src/response/dtos/success-response.dto.ts +34 -0
  52. package/src/response/http-exception-filter.spec.ts +179 -0
  53. package/src/response/http-exception-filter.ts +21 -0
  54. package/src/response/index.ts +4 -0
  55. package/src/response/response.module.ts +9 -0
  56. package/src/response/response.service.spec.ts +72 -0
  57. package/src/response/response.service.ts +20 -0
  58. package/src/testing/factories.ts +42 -0
  59. package/src/testing/index.ts +1 -0
  60. package/src/utils/date/date.service.spec.ts +104 -0
  61. package/src/utils/date/date.service.ts +19 -0
  62. package/src/utils/date/index.ts +1 -0
  63. package/src/utils/dismissible.helper.ts +9 -0
  64. package/src/utils/index.ts +3 -0
  65. package/src/validation/dismissible-input.dto.ts +47 -0
  66. package/src/validation/index.ts +1 -0
  67. package/tsconfig.json +16 -0
  68. package/tsconfig.lib.json +14 -0
  69. package/tsconfig.spec.json +12 -0
@@ -0,0 +1,313 @@
1
+ import { BadRequestException, ArgumentMetadata } from '@nestjs/common';
2
+ import { ParamValidationPipe } from './param-validation.pipe';
3
+ import { VALIDATION_CONSTANTS } from '../../validation/dismissible-input.dto';
4
+
5
+ describe('ParamValidationPipe', () => {
6
+ let pipe: ParamValidationPipe;
7
+
8
+ beforeEach(() => {
9
+ pipe = new ParamValidationPipe();
10
+ });
11
+
12
+ describe('valid values', () => {
13
+ it('should pass validation for a valid alphanumeric string', () => {
14
+ const metadata: ArgumentMetadata = {
15
+ type: 'param',
16
+ data: 'userId',
17
+ };
18
+ const result = pipe.transform('user123', metadata);
19
+ expect(result).toBe('user123');
20
+ });
21
+
22
+ it('should pass validation for a string with dashes', () => {
23
+ const metadata: ArgumentMetadata = {
24
+ type: 'param',
25
+ data: 'itemId',
26
+ };
27
+ const result = pipe.transform('item-123', metadata);
28
+ expect(result).toBe('item-123');
29
+ });
30
+
31
+ it('should pass validation for a string with underscores', () => {
32
+ const metadata: ArgumentMetadata = {
33
+ type: 'param',
34
+ data: 'userId',
35
+ };
36
+ const result = pipe.transform('user_123', metadata);
37
+ expect(result).toBe('user_123');
38
+ });
39
+
40
+ it('should pass validation for a string with mixed valid characters', () => {
41
+ const metadata: ArgumentMetadata = {
42
+ type: 'param',
43
+ data: 'itemId',
44
+ };
45
+ const result = pipe.transform('item-123_test', metadata);
46
+ expect(result).toBe('item-123_test');
47
+ });
48
+
49
+ it('should pass validation for minimum length (1 character)', () => {
50
+ const metadata: ArgumentMetadata = {
51
+ type: 'param',
52
+ data: 'userId',
53
+ };
54
+ const result = pipe.transform('a', metadata);
55
+ expect(result).toBe('a');
56
+ });
57
+
58
+ it('should pass validation for maximum length (64 characters)', () => {
59
+ const metadata: ArgumentMetadata = {
60
+ type: 'param',
61
+ data: 'itemId',
62
+ };
63
+ const validMaxLength = 'a'.repeat(VALIDATION_CONSTANTS.ID_MAX_LENGTH);
64
+ const result = pipe.transform(validMaxLength, metadata);
65
+ expect(result).toBe(validMaxLength);
66
+ });
67
+ });
68
+
69
+ describe('empty or null values', () => {
70
+ it('should throw BadRequestException for empty string', () => {
71
+ const metadata: ArgumentMetadata = {
72
+ type: 'param',
73
+ data: 'userId',
74
+ };
75
+ expect(() => pipe.transform('', metadata)).toThrow(BadRequestException);
76
+ expect(() => pipe.transform('', metadata)).toThrow('userId is required');
77
+ });
78
+
79
+ it('should throw BadRequestException for whitespace-only string', () => {
80
+ const metadata: ArgumentMetadata = {
81
+ type: 'param',
82
+ data: 'itemId',
83
+ };
84
+ expect(() => pipe.transform(' ', metadata)).toThrow(BadRequestException);
85
+ expect(() => pipe.transform(' ', metadata)).toThrow('itemId is required');
86
+ });
87
+
88
+ it('should throw BadRequestException for null value', () => {
89
+ const metadata: ArgumentMetadata = {
90
+ type: 'param',
91
+ data: 'userId',
92
+ };
93
+ expect(() => pipe.transform(null as any, metadata)).toThrow(BadRequestException);
94
+ expect(() => pipe.transform(null as any, metadata)).toThrow('userId is required');
95
+ });
96
+
97
+ it('should throw BadRequestException for undefined value', () => {
98
+ const metadata: ArgumentMetadata = {
99
+ type: 'param',
100
+ data: 'itemId',
101
+ };
102
+ expect(() => pipe.transform(undefined as any, metadata)).toThrow(BadRequestException);
103
+ expect(() => pipe.transform(undefined as any, metadata)).toThrow('itemId is required');
104
+ });
105
+
106
+ it('should use default parameter name when metadata.data is not provided', () => {
107
+ const metadata: ArgumentMetadata = {
108
+ type: 'param',
109
+ };
110
+ expect(() => pipe.transform('', metadata)).toThrow(BadRequestException);
111
+ expect(() => pipe.transform('', metadata)).toThrow('parameter is required');
112
+ });
113
+ });
114
+
115
+ describe('length validation', () => {
116
+ it('should throw BadRequestException for value below minimum length', () => {
117
+ const metadata: ArgumentMetadata = {
118
+ type: 'param',
119
+ data: 'userId',
120
+ };
121
+ expect(() => pipe.transform('', metadata)).toThrow(BadRequestException);
122
+ });
123
+
124
+ it('should throw BadRequestException for value exceeding maximum length', () => {
125
+ const metadata: ArgumentMetadata = {
126
+ type: 'param',
127
+ data: 'itemId',
128
+ };
129
+ const tooLong = 'a'.repeat(VALIDATION_CONSTANTS.ID_MAX_LENGTH + 1);
130
+ expect(() => pipe.transform(tooLong, metadata)).toThrow(BadRequestException);
131
+ expect(() => pipe.transform(tooLong, metadata)).toThrow(
132
+ `itemId must be at most ${VALIDATION_CONSTANTS.ID_MAX_LENGTH} characters`,
133
+ );
134
+ });
135
+ });
136
+
137
+ describe('pattern validation', () => {
138
+ it('should throw BadRequestException for string with spaces', () => {
139
+ const metadata: ArgumentMetadata = {
140
+ type: 'param',
141
+ data: 'userId',
142
+ };
143
+ expect(() => pipe.transform('user 123', metadata)).toThrow(BadRequestException);
144
+ expect(() => pipe.transform('user 123', metadata)).toThrow(
145
+ `userId ${VALIDATION_CONSTANTS.ID_PATTERN_MESSAGE}`,
146
+ );
147
+ });
148
+
149
+ it('should throw BadRequestException for string with special characters', () => {
150
+ const metadata: ArgumentMetadata = {
151
+ type: 'param',
152
+ data: 'itemId',
153
+ };
154
+ expect(() => pipe.transform('item@123', metadata)).toThrow(BadRequestException);
155
+ expect(() => pipe.transform('item@123', metadata)).toThrow(
156
+ `itemId ${VALIDATION_CONSTANTS.ID_PATTERN_MESSAGE}`,
157
+ );
158
+ });
159
+
160
+ it('should throw BadRequestException for string with dots', () => {
161
+ const metadata: ArgumentMetadata = {
162
+ type: 'param',
163
+ data: 'userId',
164
+ };
165
+ expect(() => pipe.transform('user.123', metadata)).toThrow(BadRequestException);
166
+ expect(() => pipe.transform('user.123', metadata)).toThrow(
167
+ `userId ${VALIDATION_CONSTANTS.ID_PATTERN_MESSAGE}`,
168
+ );
169
+ });
170
+
171
+ it('should throw BadRequestException for string with slashes', () => {
172
+ const metadata: ArgumentMetadata = {
173
+ type: 'param',
174
+ data: 'itemId',
175
+ };
176
+ expect(() => pipe.transform('item/123', metadata)).toThrow(BadRequestException);
177
+ expect(() => pipe.transform('item/123', metadata)).toThrow(
178
+ `itemId ${VALIDATION_CONSTANTS.ID_PATTERN_MESSAGE}`,
179
+ );
180
+ });
181
+
182
+ it('should throw BadRequestException for string with unicode characters', () => {
183
+ const metadata: ArgumentMetadata = {
184
+ type: 'param',
185
+ data: 'userId',
186
+ };
187
+ expect(() => pipe.transform('userñ123', metadata)).toThrow(BadRequestException);
188
+ expect(() => pipe.transform('userñ123', metadata)).toThrow(
189
+ `userId ${VALIDATION_CONSTANTS.ID_PATTERN_MESSAGE}`,
190
+ );
191
+ });
192
+
193
+ it('should throw BadRequestException for string starting with invalid character', () => {
194
+ const metadata: ArgumentMetadata = {
195
+ type: 'param',
196
+ data: 'itemId',
197
+ };
198
+ expect(() => pipe.transform('@item123', metadata)).toThrow(BadRequestException);
199
+ expect(() => pipe.transform('@item123', metadata)).toThrow(
200
+ `itemId ${VALIDATION_CONSTANTS.ID_PATTERN_MESSAGE}`,
201
+ );
202
+ });
203
+
204
+ it('should throw BadRequestException for string ending with invalid character', () => {
205
+ const metadata: ArgumentMetadata = {
206
+ type: 'param',
207
+ data: 'userId',
208
+ };
209
+ expect(() => pipe.transform('user123#', metadata)).toThrow(BadRequestException);
210
+ expect(() => pipe.transform('user123#', metadata)).toThrow(
211
+ `userId ${VALIDATION_CONSTANTS.ID_PATTERN_MESSAGE}`,
212
+ );
213
+ });
214
+ });
215
+
216
+ describe('error messages', () => {
217
+ it('should include parameter name in required error message', () => {
218
+ const metadata: ArgumentMetadata = {
219
+ type: 'param',
220
+ data: 'customParam',
221
+ };
222
+ try {
223
+ pipe.transform('', metadata);
224
+ fail('Should have thrown BadRequestException');
225
+ } catch (error) {
226
+ expect(error).toBeInstanceOf(BadRequestException);
227
+ expect((error as BadRequestException).message).toBe('customParam is required');
228
+ }
229
+ });
230
+
231
+ it('should include parameter name in length error message', () => {
232
+ const metadata: ArgumentMetadata = {
233
+ type: 'param',
234
+ data: 'testParam',
235
+ };
236
+ const tooLong = 'a'.repeat(VALIDATION_CONSTANTS.ID_MAX_LENGTH + 1);
237
+ try {
238
+ pipe.transform(tooLong, metadata);
239
+ fail('Should have thrown BadRequestException');
240
+ } catch (error) {
241
+ expect(error).toBeInstanceOf(BadRequestException);
242
+ expect((error as BadRequestException).message).toContain('testParam');
243
+ expect((error as BadRequestException).message).toContain(
244
+ `must be at most ${VALIDATION_CONSTANTS.ID_MAX_LENGTH} characters`,
245
+ );
246
+ }
247
+ });
248
+
249
+ it('should include parameter name in pattern error message', () => {
250
+ const metadata: ArgumentMetadata = {
251
+ type: 'param',
252
+ data: 'myParam',
253
+ };
254
+ try {
255
+ pipe.transform('invalid@value', metadata);
256
+ fail('Should have thrown BadRequestException');
257
+ } catch (error) {
258
+ expect(error).toBeInstanceOf(BadRequestException);
259
+ expect((error as BadRequestException).message).toContain('myParam');
260
+ expect((error as BadRequestException).message).toContain(
261
+ VALIDATION_CONSTANTS.ID_PATTERN_MESSAGE,
262
+ );
263
+ }
264
+ });
265
+ });
266
+
267
+ describe('edge cases', () => {
268
+ it('should handle numeric-only strings', () => {
269
+ const metadata: ArgumentMetadata = {
270
+ type: 'param',
271
+ data: 'itemId',
272
+ };
273
+ const result = pipe.transform('123456', metadata);
274
+ expect(result).toBe('123456');
275
+ });
276
+
277
+ it('should handle uppercase letters', () => {
278
+ const metadata: ArgumentMetadata = {
279
+ type: 'param',
280
+ data: 'userId',
281
+ };
282
+ const result = pipe.transform('USER123', metadata);
283
+ expect(result).toBe('USER123');
284
+ });
285
+
286
+ it('should handle lowercase letters', () => {
287
+ const metadata: ArgumentMetadata = {
288
+ type: 'param',
289
+ data: 'itemId',
290
+ };
291
+ const result = pipe.transform('user123', metadata);
292
+ expect(result).toBe('user123');
293
+ });
294
+
295
+ it('should handle mixed case letters', () => {
296
+ const metadata: ArgumentMetadata = {
297
+ type: 'param',
298
+ data: 'userId',
299
+ };
300
+ const result = pipe.transform('User123Item', metadata);
301
+ expect(result).toBe('User123Item');
302
+ });
303
+
304
+ it('should handle string with only dashes and underscores', () => {
305
+ const metadata: ArgumentMetadata = {
306
+ type: 'param',
307
+ data: 'itemId',
308
+ };
309
+ const result = pipe.transform('_-', metadata);
310
+ expect(result).toBe('_-');
311
+ });
312
+ });
313
+ });
@@ -0,0 +1,38 @@
1
+ import { PipeTransform, Injectable, BadRequestException, ArgumentMetadata } from '@nestjs/common';
2
+ import { VALIDATION_CONSTANTS } from '../../validation/dismissible-input.dto';
3
+
4
+ /**
5
+ * Validation pipe for userId and itemId route parameters.
6
+ * Validates:
7
+ * - Required (non-empty)
8
+ * - Length between 1-64 characters
9
+ * - Contains only alphanumeric characters, dashes, and underscores
10
+ */
11
+ @Injectable()
12
+ export class ParamValidationPipe implements PipeTransform<string, string> {
13
+ transform(value: string, metadata: ArgumentMetadata): string {
14
+ const paramName = metadata.data || 'parameter';
15
+
16
+ if (!value || value.trim() === '') {
17
+ throw new BadRequestException(`${paramName} is required`);
18
+ }
19
+
20
+ if (value.length < VALIDATION_CONSTANTS.ID_MIN_LENGTH) {
21
+ throw new BadRequestException(
22
+ `${paramName} must be at least ${VALIDATION_CONSTANTS.ID_MIN_LENGTH} character`,
23
+ );
24
+ }
25
+
26
+ if (value.length > VALIDATION_CONSTANTS.ID_MAX_LENGTH) {
27
+ throw new BadRequestException(
28
+ `${paramName} must be at most ${VALIDATION_CONSTANTS.ID_MAX_LENGTH} characters`,
29
+ );
30
+ }
31
+
32
+ if (!VALIDATION_CONSTANTS.ID_PATTERN.test(value)) {
33
+ throw new BadRequestException(`${paramName} ${VALIDATION_CONSTANTS.ID_PATTERN_MESSAGE}`);
34
+ }
35
+
36
+ return value;
37
+ }
38
+ }
@@ -0,0 +1,32 @@
1
+ import { Param } from '@nestjs/common';
2
+ import { ParamValidationPipe } from './param-validation.pipe';
3
+
4
+ /**
5
+ * Custom parameter decorator for userId.
6
+ * Combines @Param('userId') with ParamValidationPipe for validation.
7
+ *
8
+ * @example
9
+ * ```typescript
10
+ * @Get(':itemId')
11
+ * async getOrCreate(
12
+ * @UserId() userId: string,
13
+ * @ItemId() itemId: string,
14
+ * )
15
+ * ```
16
+ */
17
+ export const UserId = () => Param('userId', ParamValidationPipe);
18
+
19
+ /**
20
+ * Custom parameter decorator for itemId.
21
+ * Combines @Param('itemId') with ParamValidationPipe for validation.
22
+ *
23
+ * @example
24
+ * ```typescript
25
+ * @Get(':itemId')
26
+ * async getOrCreate(
27
+ * @UserId() userId: string,
28
+ * @ItemId() itemId: string,
29
+ * )
30
+ * ```
31
+ */
32
+ export const ItemId = () => Param('itemId', ParamValidationPipe);