@dismissible/nestjs-dismissible 0.0.2-canary.738340d.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 (62) hide show
  1. package/README.md +506 -0
  2. package/jest.config.ts +29 -0
  3. package/package.json +63 -0
  4. package/project.json +42 -0
  5. package/src/api/dismissible-item-response.dto.ts +38 -0
  6. package/src/api/dismissible-item.mapper.spec.ts +63 -0
  7. package/src/api/dismissible-item.mapper.ts +33 -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 +42 -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 +76 -0
  15. package/src/api/use-cases/get-or-create/get-or-create.controller.ts +106 -0
  16. package/src/api/use-cases/get-or-create/get-or-create.request.dto.ts +17 -0
  17. package/src/api/use-cases/get-or-create/get-or-create.response.dto.ts +7 -0
  18. package/src/api/use-cases/get-or-create/index.ts +3 -0
  19. package/src/api/use-cases/index.ts +3 -0
  20. package/src/api/use-cases/restore/index.ts +2 -0
  21. package/src/api/use-cases/restore/restore.controller.spec.ts +42 -0
  22. package/src/api/use-cases/restore/restore.controller.ts +63 -0
  23. package/src/api/use-cases/restore/restore.response.dto.ts +7 -0
  24. package/src/core/create-options.ts +9 -0
  25. package/src/core/dismissible-core.service.spec.ts +357 -0
  26. package/src/core/dismissible-core.service.ts +161 -0
  27. package/src/core/dismissible.service.spec.ts +144 -0
  28. package/src/core/dismissible.service.ts +188 -0
  29. package/src/core/hook-runner.service.spec.ts +304 -0
  30. package/src/core/hook-runner.service.ts +267 -0
  31. package/src/core/index.ts +6 -0
  32. package/src/core/lifecycle-hook.interface.ts +122 -0
  33. package/src/core/service-responses.interface.ts +34 -0
  34. package/src/dismissible.module.ts +83 -0
  35. package/src/events/dismissible.events.ts +105 -0
  36. package/src/events/events.constants.ts +21 -0
  37. package/src/events/index.ts +2 -0
  38. package/src/exceptions/dismissible.exceptions.spec.ts +50 -0
  39. package/src/exceptions/dismissible.exceptions.ts +69 -0
  40. package/src/exceptions/index.ts +1 -0
  41. package/src/index.ts +8 -0
  42. package/src/request/index.ts +2 -0
  43. package/src/request/request-context.decorator.ts +14 -0
  44. package/src/request/request-context.interface.ts +6 -0
  45. package/src/response/dtos/base-response.dto.ts +11 -0
  46. package/src/response/dtos/error-response.dto.ts +36 -0
  47. package/src/response/dtos/index.ts +3 -0
  48. package/src/response/dtos/success-response.dto.ts +34 -0
  49. package/src/response/http-exception-filter.ts +21 -0
  50. package/src/response/index.ts +4 -0
  51. package/src/response/response.module.ts +9 -0
  52. package/src/response/response.service.spec.ts +86 -0
  53. package/src/response/response.service.ts +20 -0
  54. package/src/testing/factories.ts +45 -0
  55. package/src/testing/index.ts +1 -0
  56. package/src/utils/date/date.service.spec.ts +104 -0
  57. package/src/utils/date/date.service.ts +19 -0
  58. package/src/utils/date/index.ts +1 -0
  59. package/src/utils/dismissible.helper.ts +9 -0
  60. package/src/utils/index.ts +3 -0
  61. package/tsconfig.json +13 -0
  62. package/tsconfig.lib.json +14 -0
package/README.md ADDED
@@ -0,0 +1,506 @@
1
+ # @dismissible/nestjs-dismissible
2
+
3
+ A powerful NestJS library for managing dismissible state in your applications. Perfect for feature flags, user preferences, onboarding flows, and any scenario where you need to track whether a user has dismissed or interacted with specific items.
4
+
5
+ > **Part of the Dismissible API** - This library is part of the [Dismissible API](https://dismissible.io) ecosystem. Visit [dismissible.io](https://dismissible.io) for more information and documentation.
6
+
7
+ ## Features
8
+
9
+ - 🚀 **Simple API** - Easy-to-use service methods for get-or-create, dismiss, and restore operations
10
+ - 💾 **Flexible Storage** - Default in-memory storage with support for custom storage adapters (PostgreSQL, Redis, etc.)
11
+ - 🔌 **Lifecycle Hooks** - Intercept and customize operations with pre/post hooks
12
+ - 📡 **Event-Driven** - Built-in event emission for all operations
13
+ - 🎯 **Type-Safe** - Full TypeScript support with generic metadata types
14
+ - 🛡️ **Validation** - Automatic validation of dismissible items
15
+ - 📝 **Swagger Integration** - Auto-generated API documentation
16
+ - ⚛️ **React Client** - Works out of the box with [@dismissible/react-client](https://www.npmjs.com/package/@dismissible/react-client)
17
+
18
+ ## Installation
19
+
20
+ ```bash
21
+ npm install @dismissible/nestjs-dismissible
22
+ ```
23
+
24
+ ## Getting Started
25
+
26
+ ### Basic Setup
27
+
28
+ The simplest way to get started is with the default configuration, which uses in-memory storage:
29
+
30
+ ```typescript
31
+ import { Module } from '@nestjs/common';
32
+ import { DismissibleModule } from '@dismissible/nestjs-dismissible';
33
+
34
+ @Module({
35
+ imports: [DismissibleModule.forRoot({})],
36
+ })
37
+ export class AppModule {}
38
+ ```
39
+
40
+ ### Built-in REST API
41
+
42
+ The module automatically registers REST endpoints for all operations:
43
+
44
+ - `GET /v1/user/:userId/dismissible-item/:itemId` - Get or create an item
45
+ - `DELETE /v1/user/:userId/dismissible-item/:itemId` - Dismiss an item
46
+ - `POST /v1/user/:userId/dismissible-item/:itemId` - Restore a dismissed item
47
+
48
+ Example request:
49
+
50
+ ```bash
51
+ # Get or create an item
52
+ curl http://localhost:3000/v1/user/user-123/dismissible-item/welcome-banner?metadata=version:2&metadata=category:onboarding
53
+
54
+ # Dismiss an item
55
+ curl -X DELETE http://localhost:3000/v1/user/user-123/dismissible-item/welcome-banner
56
+
57
+ # Restore a dismissed item
58
+ curl -X POST http://localhost:3000/v1/user/user-123/dismissible-item/welcome-banner
59
+ ```
60
+
61
+ ### React Client Integration
62
+
63
+ This library works seamlessly with the [@dismissible/react-client](https://www.npmjs.com/package/@dismissible/react-client) package. Once your NestJS backend is set up with the built-in REST API endpoints, you can use the React client in your frontend:
64
+
65
+ ```bash
66
+ npm install @dismissible/react-client
67
+ ```
68
+
69
+ ```typescript
70
+ import { DismissibleProvider, useDismissible } from '@dismissible/react-client';
71
+
72
+ function App() {
73
+ return (
74
+ <DismissibleProvider
75
+ apiUrl="http://localhost:3000"
76
+ userId="user-123"
77
+ >
78
+ <WelcomeBanner />
79
+ </DismissibleProvider>
80
+ );
81
+ }
82
+
83
+ function WelcomeBanner() {
84
+ const { item, dismiss, isLoading } = useDismissible('welcome-banner');
85
+
86
+ if (isLoading) return <div>Loading...</div>;
87
+ if (item?.dismissedAt) return null;
88
+
89
+ return (
90
+ <div>
91
+ <h2>Welcome!</h2>
92
+ <button onClick={() => dismiss()}>Dismiss</button>
93
+ </div>
94
+ );
95
+ }
96
+ ```
97
+
98
+ The React client automatically uses the built-in REST API endpoints, so no additional configuration is needed on the backend.
99
+
100
+ ## Advanced Usage
101
+
102
+ ### Using PostgreSQL Storage
103
+
104
+ To persist dismissible items in a PostgreSQL database, use the `PostgresStorageModule`:
105
+
106
+ ```typescript
107
+ import { Module } from '@nestjs/common';
108
+ import { DismissibleModule } from '@dismissible/nestjs-dismissible';
109
+ import { PostgresStorageModule } from '@dismissible/nestjs-postgres-storage';
110
+
111
+ @Module({
112
+ imports: [
113
+ DismissibleModule.forRoot({
114
+ storage: PostgresStorageModule,
115
+ }),
116
+ ],
117
+ })
118
+ export class AppModule {}
119
+ ```
120
+
121
+ **Prerequisites:**
122
+
123
+ 1. Install the PostgreSQL storage package:
124
+
125
+ ```bash
126
+ npm install @dismissible/nestjs-postgres-storage
127
+ ```
128
+
129
+ 2. Set up your database connection string:
130
+
131
+ ```env
132
+ DISMISSIBLE_POSTGRES_STORAGE_CONNECTION_STRING=postgresql://user:password@localhost:5432/dismissible
133
+ ```
134
+
135
+ 3. Run Prisma migrations (if using Prisma):
136
+ ```bash
137
+ npx prisma migrate dev
138
+ ```
139
+
140
+ The PostgreSQL adapter uses Prisma and automatically handles schema migrations. The storage persists all dismissible items across application restarts.
141
+
142
+ ### Using the Service
143
+
144
+ Instead of using the built-in REST API endpoints, you can inject `DismissibleService` directly into your controllers or other services for more control:
145
+
146
+ ```typescript
147
+ import { Controller, Get, Param, Delete, Post } from '@nestjs/common';
148
+ import { DismissibleService } from '@dismissible/nestjs-dismissible';
149
+
150
+ @Controller('features')
151
+ export class FeaturesController {
152
+ constructor(private readonly dismissibleService: DismissibleService) {}
153
+
154
+ @Get(':userId/items/:itemId')
155
+ async getOrCreateItem(@Param('userId') userId: string, @Param('itemId') itemId: string) {
156
+ const result = await this.dismissibleService.getOrCreate(
157
+ itemId,
158
+ userId,
159
+ {
160
+ metadata: { version: '1.0', category: 'onboarding' },
161
+ },
162
+ undefined, // optional request context
163
+ );
164
+
165
+ return {
166
+ item: result.item,
167
+ wasCreated: result.created,
168
+ };
169
+ }
170
+
171
+ @Delete(':userId/items/:itemId')
172
+ async dismissItem(@Param('userId') userId: string, @Param('itemId') itemId: string) {
173
+ const result = await this.dismissibleService.dismiss(itemId, userId);
174
+ return { item: result.item };
175
+ }
176
+
177
+ @Post(':userId/items/:itemId/restore')
178
+ async restoreItem(@Param('userId') userId: string, @Param('itemId') itemId: string) {
179
+ const result = await this.dismissibleService.restore(itemId, userId);
180
+ return { item: result.item };
181
+ }
182
+ }
183
+ ```
184
+
185
+ ### Custom Lifecycle Hooks
186
+
187
+ Lifecycle hooks allow you to intercept operations and add custom logic, validation, or mutations:
188
+
189
+ ```typescript
190
+ import { Injectable } from '@nestjs/common';
191
+ import { IDismissibleLifecycleHook, IHookResult } from '@dismissible/nestjs-dismissible';
192
+ import { BaseMetadata } from '@dismissible/nestjs-dismissible-item';
193
+
194
+ @Injectable()
195
+ export class AuditHook implements IDismissibleLifecycleHook<BaseMetadata> {
196
+ // Lower priority runs first (default is 0)
197
+ readonly priority = 10;
198
+
199
+ async onBeforeDismiss(
200
+ itemId: string,
201
+ userId: string,
202
+ context?: IRequestContext,
203
+ ): Promise<IHookResult> {
204
+ // Block dismissal of critical items
205
+ if (itemId.startsWith('critical-')) {
206
+ return {
207
+ proceed: false,
208
+ reason: 'Cannot dismiss critical items',
209
+ };
210
+ }
211
+
212
+ // Allow the operation to proceed
213
+ return { proceed: true };
214
+ }
215
+
216
+ async onAfterCreate(
217
+ itemId: string,
218
+ item: DismissibleItemDto<BaseMetadata>,
219
+ userId: string,
220
+ context?: IRequestContext,
221
+ ): Promise<void> {
222
+ // Log item creation for analytics
223
+ console.log(`Item created: ${itemId} for user ${userId}`);
224
+ }
225
+
226
+ // Mutate item ID before operation
227
+ async onBeforeGetOrCreate(
228
+ itemId: string,
229
+ userId: string,
230
+ context?: IRequestContext,
231
+ ): Promise<IHookResult> {
232
+ // Normalize item IDs (e.g., lowercase)
233
+ return {
234
+ proceed: true,
235
+ mutations: {
236
+ id: itemId.toLowerCase(),
237
+ },
238
+ };
239
+ }
240
+ }
241
+ ```
242
+
243
+ Register hooks in your module:
244
+
245
+ ```typescript
246
+ import { DismissibleModule } from '@dismissible/nestjs-dismissible';
247
+ import { AuditHook } from './hooks/audit.hook';
248
+
249
+ @Module({
250
+ imports: [
251
+ DismissibleModule.forRoot({
252
+ hooks: [AuditHook],
253
+ }),
254
+ ],
255
+ })
256
+ export class AppModule {}
257
+ ```
258
+
259
+ ### Listening to Events
260
+
261
+ The library emits events for all operations. Listen to them using NestJS's `EventEmitter2`:
262
+
263
+ ```typescript
264
+ import { Injectable } from '@nestjs/common';
265
+ import { OnEvent } from '@nestjs/event-emitter';
266
+ import {
267
+ ItemCreatedEvent,
268
+ ItemDismissedEvent,
269
+ ItemRestoredEvent,
270
+ ItemRetrievedEvent,
271
+ DismissibleEvents,
272
+ } from '@dismissible/nestjs-dismissible';
273
+
274
+ @Injectable()
275
+ export class AnalyticsService {
276
+ @OnEvent(DismissibleEvents.ITEM_CREATED)
277
+ handleItemCreated(event: ItemCreatedEvent) {
278
+ // Track item creation in analytics
279
+ console.log(`Analytics: Item ${event.id} created for user ${event.userId}`);
280
+ }
281
+
282
+ @OnEvent(DismissibleEvents.ITEM_DISMISSED)
283
+ handleItemDismissed(event: ItemDismissedEvent) {
284
+ // Track dismissals
285
+ console.log(`Analytics: Item ${event.id} dismissed by user ${event.userId}`);
286
+ }
287
+
288
+ @OnEvent(DismissibleEvents.ITEM_RESTORED)
289
+ handleItemRestored(event: ItemRestoredEvent) {
290
+ // Track restorations
291
+ console.log(`Analytics: Item ${event.id} restored by user ${event.userId}`);
292
+ }
293
+ }
294
+ ```
295
+
296
+ ### Custom Metadata Types
297
+
298
+ Define custom metadata types for type-safe item properties:
299
+
300
+ ```typescript
301
+ import { BaseMetadata } from '@dismissible/nestjs-dismissible-item';
302
+
303
+ interface OnboardingMetadata extends BaseMetadata {
304
+ step: number;
305
+ completedAt?: string;
306
+ skipped: boolean;
307
+ }
308
+
309
+ // Use in your service
310
+ @Injectable()
311
+ export class OnboardingService {
312
+ constructor(private readonly dismissibleService: DismissibleService<OnboardingMetadata>) {}
313
+
314
+ async trackStep(userId: string, step: number) {
315
+ const result = await this.dismissibleService.getOrCreate(
316
+ `onboarding-step-${step}`,
317
+ userId,
318
+ {
319
+ metadata: {
320
+ step,
321
+ skipped: false,
322
+ },
323
+ },
324
+ undefined,
325
+ );
326
+
327
+ return result.item;
328
+ }
329
+ }
330
+ ```
331
+
332
+ ### Custom Logger
333
+
334
+ Provide a custom logger implementation:
335
+
336
+ ```typescript
337
+ import { Injectable } from '@nestjs/common';
338
+ import { IDismissibleLogger } from '@dismissible/nestjs-logger';
339
+ import { DismissibleModule } from '@dismissible/nestjs-dismissible';
340
+
341
+ @Injectable()
342
+ export class CustomLogger implements IDismissibleLogger {
343
+ debug(message: string, context?: any) {
344
+ // Your custom logging logic
345
+ console.log(`[DEBUG] ${message}`, context);
346
+ }
347
+
348
+ info(message: string, context?: any) {
349
+ console.log(`[INFO] ${message}`, context);
350
+ }
351
+
352
+ warn(message: string, context?: any) {
353
+ console.warn(`[WARN] ${message}`, context);
354
+ }
355
+
356
+ error(message: string, context?: any) {
357
+ console.error(`[ERROR] ${message}`, context);
358
+ }
359
+ }
360
+
361
+ @Module({
362
+ imports: [
363
+ DismissibleModule.forRoot({
364
+ logger: CustomLogger,
365
+ }),
366
+ ],
367
+ })
368
+ export class AppModule {}
369
+ ```
370
+
371
+ ## API Reference
372
+
373
+ ### DismissibleService
374
+
375
+ The main service for interacting with dismissible items.
376
+
377
+ #### Methods
378
+
379
+ **`getOrCreate(itemId, userId, options?, context?)`**
380
+
381
+ Retrieves an existing item or creates a new one if it doesn't exist.
382
+
383
+ - `itemId: string` - Unique identifier for the item
384
+ - `userId: string` - User identifier (required)
385
+ - `options?: ICreateItemOptions<TMetadata>` - Optional creation options
386
+ - `metadata?: TMetadata` - Custom metadata to attach to the item
387
+ - `context?: IRequestContext` - Optional request context for tracing
388
+
389
+ Returns: `Promise<IGetOrCreateServiceResponse<TMetadata>>`
390
+
391
+ **`dismiss(itemId, userId, context?)`**
392
+
393
+ Marks an item as dismissed.
394
+
395
+ - `itemId: string` - Item identifier
396
+ - `userId: string` - User identifier
397
+ - `context?: IRequestContext` - Optional request context
398
+
399
+ Returns: `Promise<IDismissServiceResponse<TMetadata>>`
400
+
401
+ **`restore(itemId, userId, context?)`**
402
+
403
+ Restores a previously dismissed item.
404
+
405
+ - `itemId: string` - Item identifier
406
+ - `userId: string` - User identifier
407
+ - `context?: IRequestContext` - Optional request context
408
+
409
+ Returns: `Promise<IRestoreServiceResponse<TMetadata>>`
410
+
411
+ ### Module Configuration
412
+
413
+ ```typescript
414
+ interface IDismissibleModuleOptions<TMetadata extends BaseMetadata> {
415
+ // Custom storage module (defaults to in-memory storage)
416
+ storage?: DynamicModule | Type<any>;
417
+
418
+ // Custom logger implementation
419
+ logger?: Type<IDismissibleLogger>;
420
+
421
+ // Lifecycle hooks to register
422
+ hooks?: Type<IDismissibleLifecycleHook<TMetadata>>[];
423
+ }
424
+ ```
425
+
426
+ ## Events
427
+
428
+ The library emits the following events:
429
+
430
+ - `DismissibleEvents.ITEM_CREATED` - Emitted when a new item is created
431
+ - `DismissibleEvents.ITEM_RETRIEVED` - Emitted when an existing item is retrieved
432
+ - `DismissibleEvents.ITEM_DISMISSED` - Emitted when an item is dismissed
433
+ - `DismissibleEvents.ITEM_RESTORED` - Emitted when an item is restored
434
+
435
+ All events include:
436
+
437
+ - `id: string` - The item identifier
438
+ - `item: DismissibleItemDto<TMetadata>` - The current item state
439
+ - `userId: string` - The user identifier
440
+ - `context?: IRequestContext` - Optional request context
441
+
442
+ Dismiss and restore events also include:
443
+
444
+ - `previousItem: DismissibleItemDto<TMetadata>` - The item state before the operation
445
+
446
+ ## Lifecycle Hooks
447
+
448
+ Hooks can implement any of the following methods:
449
+
450
+ - `onBeforeGetOrCreate()` - Called before get-or-create operation
451
+ - `onAfterGetOrCreate()` - Called after get-or-create operation
452
+ - `onBeforeCreate()` - Called before creating a new item
453
+ - `onAfterCreate()` - Called after creating a new item
454
+ - `onBeforeDismiss()` - Called before dismissing an item
455
+ - `onAfterDismiss()` - Called after dismissing an item
456
+ - `onBeforeRestore()` - Called before restoring an item
457
+ - `onAfterRestore()` - Called after restoring an item
458
+
459
+ Hooks can:
460
+
461
+ - **Block operations** by returning `{ proceed: false, reason: string }`
462
+ - **Mutate parameters** by returning `{ proceed: true, mutations: { id?, userId?, context? } }`
463
+ - **Perform side effects** in post-hooks (no return value needed)
464
+
465
+ Hooks are executed in priority order (lower numbers first).
466
+
467
+ ## Storage Adapters
468
+
469
+ ### In-Memory Storage (Default)
470
+
471
+ The default storage adapter stores items in memory. Data is lost on application restart.
472
+
473
+ ### PostgreSQL Storage
474
+
475
+ Use `PostgresStorageModule` for persistent storage. See the [Using PostgreSQL Storage](#using-postgresql-storage) section above.
476
+
477
+ ### Custom Storage Adapter
478
+
479
+ Implement the `IDismissibleStorage` interface to create a custom storage adapter:
480
+
481
+ ```typescript
482
+ import { Injectable } from '@nestjs/common';
483
+ import { IDismissibleStorage } from '@dismissible/nestjs-storage';
484
+ import { DismissibleItemDto, BaseMetadata } from '@dismissible/nestjs-dismissible-item';
485
+
486
+ @Injectable()
487
+ export class RedisStorageAdapter<
488
+ TMetadata extends BaseMetadata,
489
+ > implements IDismissibleStorage<TMetadata> {
490
+ async get(userId: string, itemId: string): Promise<DismissibleItemDto<TMetadata> | null> {
491
+ // Your implementation
492
+ }
493
+
494
+ async create(userId: string, item: DismissibleItemDto<TMetadata>): Promise<void> {
495
+ // Your implementation
496
+ }
497
+
498
+ async update(userId: string, item: DismissibleItemDto<TMetadata>): Promise<void> {
499
+ // Your implementation
500
+ }
501
+ }
502
+ ```
503
+
504
+ ## License
505
+
506
+ MIT
package/jest.config.ts ADDED
@@ -0,0 +1,29 @@
1
+ export default {
2
+ displayName: 'dismissible',
3
+ preset: '../../jest.preset.js',
4
+ testEnvironment: 'node',
5
+ transform: {
6
+ '^.+\\.[tj]s$': ['ts-jest', { tsconfig: '<rootDir>/tsconfig.json' }],
7
+ },
8
+ moduleFileExtensions: ['ts', 'js', 'html'],
9
+ coverageDirectory: '../../coverage/libs/dismissible',
10
+ transformIgnorePatterns: ['node_modules/(?!(uuid)/)'],
11
+ collectCoverageFrom: [
12
+ 'src/**/*.ts',
13
+ '!src/**/*.spec.ts',
14
+ '!src/**/*.interface.ts',
15
+ '!src/**/*.dto.ts',
16
+ '!src/**/*.enum.ts',
17
+ '!src/**/index.ts',
18
+ '!src/**/*.module.ts',
19
+ ],
20
+ coverageReporters: ['text', 'text-summary', 'html'],
21
+ coverageThreshold: {
22
+ global: {
23
+ branches: 81,
24
+ functions: 84,
25
+ lines: 92,
26
+ statements: 92,
27
+ },
28
+ },
29
+ };
package/package.json ADDED
@@ -0,0 +1,63 @@
1
+ {
2
+ "name": "@dismissible/nestjs-dismissible",
3
+ "version": "0.0.2-canary.738340d.0",
4
+ "description": "Dismissible state management library for NestJS applications",
5
+ "main": "./src/index.js",
6
+ "types": "./src/index.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "import": "./src/index.mjs",
10
+ "require": "./src/index.js",
11
+ "types": "./src/index.d.ts"
12
+ }
13
+ },
14
+ "dependencies": {
15
+ "@nestjs/event-emitter": "^3.0.0",
16
+ "uuid": "^11.0.0"
17
+ },
18
+ "peerDependencies": {
19
+ "@nestjs/common": "^11.0.0",
20
+ "@nestjs/core": "^11.0.0",
21
+ "@nestjs/swagger": "^11.0.0",
22
+ "@dismissible/nestjs-dismissible-item": "^0.0.2-canary.738340d.0",
23
+ "@dismissible/nestjs-storage": "^0.0.2-canary.738340d.0",
24
+ "class-validator": "^0.14.0",
25
+ "class-transformer": "^0.5.0"
26
+ },
27
+ "peerDependenciesMeta": {
28
+ "@nestjs/common": {
29
+ "optional": false
30
+ },
31
+ "@nestjs/core": {
32
+ "optional": false
33
+ },
34
+ "@nestjs/swagger": {
35
+ "optional": false
36
+ },
37
+ "dismissible/nestjs-dismissible-item": {
38
+ "optional": false
39
+ },
40
+ "@dismissible/nestjs-storage": {
41
+ "optional": false
42
+ },
43
+ "class-validator": {
44
+ "optional": false
45
+ },
46
+ "class-transformer": {
47
+ "optional": false
48
+ }
49
+ },
50
+ "keywords": [
51
+ "nestjs",
52
+ "dismissible",
53
+ "state-management",
54
+ "feature-flags",
55
+ "dismissals"
56
+ ],
57
+ "author": "",
58
+ "license": "MIT",
59
+ "repository": {
60
+ "type": "git",
61
+ "url": ""
62
+ }
63
+ }
package/project.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "dismissible",
3
+ "$schema": "../../node_modules/nx/schemas/project-schema.json",
4
+ "sourceRoot": "libs/dismissible/src",
5
+ "projectType": "library",
6
+ "tags": [],
7
+ "targets": {
8
+ "build": {
9
+ "executor": "@nx/js:tsc",
10
+ "outputs": ["{options.outputPath}"],
11
+ "options": {
12
+ "outputPath": "dist/libs/dismissible",
13
+ "main": "libs/dismissible/src/index.ts",
14
+ "tsConfig": "libs/dismissible/tsconfig.lib.json",
15
+ "assets": ["libs/dismissible/package.json", "libs/dismissible/README.md"],
16
+ "generatePackageJson": true
17
+ }
18
+ },
19
+ "lint": {
20
+ "executor": "@nx/eslint:lint",
21
+ "outputs": ["{options.outputFile}"],
22
+ "options": {
23
+ "lintFilePatterns": ["libs/dismissible/**/*.ts"]
24
+ }
25
+ },
26
+ "test": {
27
+ "executor": "@nx/jest:jest",
28
+ "outputs": ["{workspaceRoot}/coverage/libs/dismissible"],
29
+ "options": {
30
+ "jestConfig": "libs/dismissible/jest.config.ts",
31
+ "passWithNoTests": true
32
+ }
33
+ },
34
+ "npm-publish": {
35
+ "executor": "nx:run-commands",
36
+ "options": {
37
+ "command": "npm publish --access public",
38
+ "cwd": "dist/libs/dismissible"
39
+ }
40
+ }
41
+ }
42
+ }
@@ -0,0 +1,38 @@
1
+ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
2
+
3
+ /**
4
+ * Response DTO for a dismissible item.
5
+ */
6
+ export class DismissibleItemResponseDto {
7
+ @ApiProperty({
8
+ description: 'Unique identifier for the item',
9
+ example: 'welcome-banner-v2',
10
+ })
11
+ itemId!: string;
12
+
13
+ @ApiProperty({
14
+ description: 'User identifier who created the item',
15
+ example: 'user-123',
16
+ })
17
+ userId!: string;
18
+
19
+ @ApiProperty({
20
+ description: 'When the item was created (ISO 8601)',
21
+ example: '2024-01-15T10:30:00.000Z',
22
+ })
23
+ createdAt!: string;
24
+
25
+ @ApiPropertyOptional({
26
+ description: 'When the item was dismissed (ISO 8601)',
27
+ example: '2024-01-15T12:00:00.000Z',
28
+ })
29
+ dismissedAt?: string;
30
+
31
+ @ApiPropertyOptional({
32
+ description: 'Optional metadata associated with the item',
33
+ example: { version: 2, category: 'promotional' },
34
+ type: 'object',
35
+ additionalProperties: true,
36
+ })
37
+ metadata?: Record<string, unknown>;
38
+ }