@dismissible/nestjs-dismissible 0.0.1

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 (179) hide show
  1. package/README.md +490 -0
  2. package/package.json +68 -0
  3. package/src/api/dismissible-item-response.dto.d.ts +9 -0
  4. package/src/api/dismissible-item-response.dto.js +40 -0
  5. package/src/api/dismissible-item-response.dto.js.map +1 -0
  6. package/src/api/dismissible-item.mapper.d.ts +12 -0
  7. package/src/api/dismissible-item.mapper.js +30 -0
  8. package/src/api/dismissible-item.mapper.js.map +1 -0
  9. package/src/api/dismissible-item.mapper.spec.d.ts +1 -0
  10. package/src/api/dismissible-item.mapper.spec.js +43 -0
  11. package/src/api/dismissible-item.mapper.spec.js.map +1 -0
  12. package/src/api/index.d.ts +4 -0
  13. package/src/api/index.js +8 -0
  14. package/src/api/index.js.map +1 -0
  15. package/src/api/use-cases/api-tags.constants.d.ts +4 -0
  16. package/src/api/use-cases/api-tags.constants.js +8 -0
  17. package/src/api/use-cases/api-tags.constants.js.map +1 -0
  18. package/src/api/use-cases/dismiss/dismiss.controller.d.ts +15 -0
  19. package/src/api/use-cases/dismiss/dismiss.controller.js +74 -0
  20. package/src/api/use-cases/dismiss/dismiss.controller.js.map +1 -0
  21. package/src/api/use-cases/dismiss/dismiss.controller.spec.d.ts +1 -0
  22. package/src/api/use-cases/dismiss/dismiss.controller.spec.js +37 -0
  23. package/src/api/use-cases/dismiss/dismiss.controller.spec.js.map +1 -0
  24. package/src/api/use-cases/dismiss/dismiss.response.dto.d.ts +12 -0
  25. package/src/api/use-cases/dismiss/dismiss.response.dto.js +12 -0
  26. package/src/api/use-cases/dismiss/dismiss.response.dto.js.map +1 -0
  27. package/src/api/use-cases/dismiss/index.d.ts +2 -0
  28. package/src/api/use-cases/dismiss/index.js +6 -0
  29. package/src/api/use-cases/dismiss/index.js.map +1 -0
  30. package/src/api/use-cases/get-or-create/get-or-create.controller.d.ts +15 -0
  31. package/src/api/use-cases/get-or-create/get-or-create.controller.js +70 -0
  32. package/src/api/use-cases/get-or-create/get-or-create.controller.js.map +1 -0
  33. package/src/api/use-cases/get-or-create/get-or-create.controller.spec.d.ts +1 -0
  34. package/src/api/use-cases/get-or-create/get-or-create.controller.spec.js +32 -0
  35. package/src/api/use-cases/get-or-create/get-or-create.controller.spec.js.map +1 -0
  36. package/src/api/use-cases/get-or-create/get-or-create.response.dto.d.ts +12 -0
  37. package/src/api/use-cases/get-or-create/get-or-create.response.dto.js +12 -0
  38. package/src/api/use-cases/get-or-create/get-or-create.response.dto.js.map +1 -0
  39. package/src/api/use-cases/get-or-create/index.d.ts +2 -0
  40. package/src/api/use-cases/get-or-create/index.js +6 -0
  41. package/src/api/use-cases/get-or-create/index.js.map +1 -0
  42. package/src/api/use-cases/index.d.ts +3 -0
  43. package/src/api/use-cases/index.js +7 -0
  44. package/src/api/use-cases/index.js.map +1 -0
  45. package/src/api/use-cases/restore/index.d.ts +2 -0
  46. package/src/api/use-cases/restore/index.js +6 -0
  47. package/src/api/use-cases/restore/index.js.map +1 -0
  48. package/src/api/use-cases/restore/restore.controller.d.ts +15 -0
  49. package/src/api/use-cases/restore/restore.controller.js +74 -0
  50. package/src/api/use-cases/restore/restore.controller.js.map +1 -0
  51. package/src/api/use-cases/restore/restore.controller.spec.d.ts +1 -0
  52. package/src/api/use-cases/restore/restore.controller.spec.js +37 -0
  53. package/src/api/use-cases/restore/restore.controller.spec.js.map +1 -0
  54. package/src/api/use-cases/restore/restore.response.dto.d.ts +12 -0
  55. package/src/api/use-cases/restore/restore.response.dto.js +12 -0
  56. package/src/api/use-cases/restore/restore.response.dto.js.map +1 -0
  57. package/src/api/validation/index.d.ts +2 -0
  58. package/src/api/validation/index.js +6 -0
  59. package/src/api/validation/index.js.map +1 -0
  60. package/src/api/validation/param-validation.pipe.d.ts +11 -0
  61. package/src/api/validation/param-validation.pipe.js +36 -0
  62. package/src/api/validation/param-validation.pipe.js.map +1 -0
  63. package/src/api/validation/param-validation.pipe.spec.d.ts +1 -0
  64. package/src/api/validation/param-validation.pipe.spec.js +269 -0
  65. package/src/api/validation/param-validation.pipe.spec.js.map +1 -0
  66. package/src/api/validation/param.decorators.d.ts +28 -0
  67. package/src/api/validation/param.decorators.js +36 -0
  68. package/src/api/validation/param.decorators.js.map +1 -0
  69. package/src/core/dismissible-core.service.d.ts +56 -0
  70. package/src/core/dismissible-core.service.js +147 -0
  71. package/src/core/dismissible-core.service.js.map +1 -0
  72. package/src/core/dismissible-core.service.spec.d.ts +1 -0
  73. package/src/core/dismissible-core.service.spec.js +309 -0
  74. package/src/core/dismissible-core.service.spec.js.map +1 -0
  75. package/src/core/dismissible.service.d.ts +45 -0
  76. package/src/core/dismissible.service.js +127 -0
  77. package/src/core/dismissible.service.js.map +1 -0
  78. package/src/core/dismissible.service.spec.d.ts +1 -0
  79. package/src/core/dismissible.service.spec.js +159 -0
  80. package/src/core/dismissible.service.spec.js.map +1 -0
  81. package/src/core/hook-runner.service.d.ts +88 -0
  82. package/src/core/hook-runner.service.js +226 -0
  83. package/src/core/hook-runner.service.js.map +1 -0
  84. package/src/core/hook-runner.service.spec.d.ts +1 -0
  85. package/src/core/hook-runner.service.spec.js +538 -0
  86. package/src/core/hook-runner.service.spec.js.map +1 -0
  87. package/src/core/index.d.ts +5 -0
  88. package/src/core/index.js +9 -0
  89. package/src/core/index.js.map +1 -0
  90. package/src/core/lifecycle-hook.interface.d.ts +1 -0
  91. package/src/core/lifecycle-hook.interface.js +7 -0
  92. package/src/core/lifecycle-hook.interface.js.map +1 -0
  93. package/src/core/service-responses.interface.d.ts +28 -0
  94. package/src/core/service-responses.interface.js +3 -0
  95. package/src/core/service-responses.interface.js.map +1 -0
  96. package/src/dismissible.module.d.ts +13 -0
  97. package/src/dismissible.module.integration.spec.d.ts +1 -0
  98. package/src/dismissible.module.integration.spec.js +529 -0
  99. package/src/dismissible.module.integration.spec.js.map +1 -0
  100. package/src/dismissible.module.js +77 -0
  101. package/src/dismissible.module.js.map +1 -0
  102. package/src/events/dismissible.events.d.ts +45 -0
  103. package/src/events/dismissible.events.js +53 -0
  104. package/src/events/dismissible.events.js.map +1 -0
  105. package/src/events/events.constants.d.ts +17 -0
  106. package/src/events/events.constants.js +17 -0
  107. package/src/events/events.constants.js.map +1 -0
  108. package/src/events/index.d.ts +2 -0
  109. package/src/events/index.js +6 -0
  110. package/src/events/index.js.map +1 -0
  111. package/src/exceptions/dismissible.exceptions.d.ts +26 -0
  112. package/src/exceptions/dismissible.exceptions.js +49 -0
  113. package/src/exceptions/dismissible.exceptions.js.map +1 -0
  114. package/src/exceptions/dismissible.exceptions.spec.d.ts +1 -0
  115. package/src/exceptions/dismissible.exceptions.spec.js +40 -0
  116. package/src/exceptions/dismissible.exceptions.spec.js.map +1 -0
  117. package/src/exceptions/index.d.ts +1 -0
  118. package/src/exceptions/index.js +5 -0
  119. package/src/exceptions/index.js.map +1 -0
  120. package/src/index.d.ts +8 -0
  121. package/src/index.js +12 -0
  122. package/src/index.js.map +1 -0
  123. package/src/response/dtos/base-response.dto.d.ts +6 -0
  124. package/src/response/dtos/base-response.dto.js +18 -0
  125. package/src/response/dtos/base-response.dto.js.map +1 -0
  126. package/src/response/dtos/error-response.dto.d.ts +19 -0
  127. package/src/response/dtos/error-response.dto.js +39 -0
  128. package/src/response/dtos/error-response.dto.js.map +1 -0
  129. package/src/response/dtos/index.d.ts +3 -0
  130. package/src/response/dtos/index.js +7 -0
  131. package/src/response/dtos/index.js.map +1 -0
  132. package/src/response/dtos/success-response.dto.d.ts +28 -0
  133. package/src/response/dtos/success-response.dto.js +34 -0
  134. package/src/response/dtos/success-response.dto.js.map +1 -0
  135. package/src/response/http-exception-filter.d.ts +4 -0
  136. package/src/response/http-exception-filter.js +24 -0
  137. package/src/response/http-exception-filter.js.map +1 -0
  138. package/src/response/http-exception-filter.spec.d.ts +1 -0
  139. package/src/response/http-exception-filter.spec.js +137 -0
  140. package/src/response/http-exception-filter.spec.js.map +1 -0
  141. package/src/response/index.d.ts +4 -0
  142. package/src/response/index.js +8 -0
  143. package/src/response/index.js.map +1 -0
  144. package/src/response/response.module.d.ts +2 -0
  145. package/src/response/response.module.js +17 -0
  146. package/src/response/response.module.js.map +1 -0
  147. package/src/response/response.service.d.ts +6 -0
  148. package/src/response/response.service.js +25 -0
  149. package/src/response/response.service.js.map +1 -0
  150. package/src/response/response.service.spec.d.ts +1 -0
  151. package/src/response/response.service.spec.js +58 -0
  152. package/src/response/response.service.spec.js.map +1 -0
  153. package/src/testing/factories.d.ts +14 -0
  154. package/src/testing/factories.js +58 -0
  155. package/src/testing/factories.js.map +1 -0
  156. package/src/testing/index.d.ts +1 -0
  157. package/src/testing/index.js +5 -0
  158. package/src/testing/index.js.map +1 -0
  159. package/src/utils/date/date.service.d.ts +8 -0
  160. package/src/utils/date/date.service.js +24 -0
  161. package/src/utils/date/date.service.js.map +1 -0
  162. package/src/utils/date/date.service.spec.d.ts +1 -0
  163. package/src/utils/date/date.service.spec.js +83 -0
  164. package/src/utils/date/date.service.spec.js.map +1 -0
  165. package/src/utils/date/index.d.ts +1 -0
  166. package/src/utils/date/index.js +5 -0
  167. package/src/utils/date/index.js.map +1 -0
  168. package/src/utils/dismissible.helper.d.ts +4 -0
  169. package/src/utils/dismissible.helper.js +15 -0
  170. package/src/utils/dismissible.helper.js.map +1 -0
  171. package/src/utils/index.d.ts +3 -0
  172. package/src/utils/index.js +7 -0
  173. package/src/utils/index.js.map +1 -0
  174. package/src/validation/dismissible-input.dto.d.ts +21 -0
  175. package/src/validation/dismissible-input.dto.js +54 -0
  176. package/src/validation/dismissible-input.dto.js.map +1 -0
  177. package/src/validation/index.d.ts +1 -0
  178. package/src/validation/index.js +5 -0
  179. package/src/validation/index.js.map +1 -0
package/README.md ADDED
@@ -0,0 +1,490 @@
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
+ - **High Performance** - Built on Fastify for maximum throughput
11
+ - **Flexible Storage** - Default in-memory storage with support for custom storage adapters (PostgreSQL, Redis, etc.)
12
+ - **Lifecycle Hooks** - Intercept and customize operations with pre/post hooks
13
+ - **JWT Authentication** - Optional JWT auth hook for securing endpoints with OIDC providers
14
+ - **Event-Driven** - Built-in event emission for all operations
15
+ - **Type-Safe** - Full TypeScript support
16
+ - **Validation** - Automatic validation of dismissible items
17
+ - **Swagger Integration** - Auto-generated API documentation
18
+ - **React Client** - Works out of the box with [@dismissible/react-client](https://www.npmjs.com/package/@dismissible/react-client)
19
+
20
+ ## Installation
21
+
22
+ ```bash
23
+ npm install @dismissible/nestjs-dismissible
24
+ ```
25
+
26
+ ## Getting Started
27
+
28
+ ### Basic Setup
29
+
30
+ The simplest way to get started is with the default configuration, which uses in-memory storage:
31
+
32
+ ```typescript
33
+ import { Module } from '@nestjs/common';
34
+ import { DismissibleModule } from '@dismissible/nestjs-dismissible';
35
+
36
+ @Module({
37
+ imports: [DismissibleModule.forRoot({})],
38
+ })
39
+ export class AppModule {}
40
+ ```
41
+
42
+ ### Built-in REST API
43
+
44
+ The module automatically registers REST endpoints for all operations:
45
+
46
+ - `GET /v1/users/:userId/items/:itemId` - Get or create an item
47
+ - `DELETE /v1/users/:userId/items/:itemId` - Dismiss an item
48
+ - `POST /v1/users/:userId/items/:itemId` - Restore a dismissed item
49
+
50
+ Example request:
51
+
52
+ ```bash
53
+ # Get or create an item
54
+ curl http://localhost:3000/v1/users/user-123/items/welcome-banner
55
+
56
+ # Dismiss an item
57
+ curl -X DELETE http://localhost:3000/v1/users/user-123/items/welcome-banner
58
+
59
+ # Restore a dismissed item
60
+ curl -X POST http://localhost:3000/v1/users/user-123/items/welcome-banner
61
+ ```
62
+
63
+ ### React Client Integration
64
+
65
+ 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:
66
+
67
+ ```bash
68
+ npm install @dismissible/react-client
69
+ ```
70
+
71
+ ```typescript
72
+ import { DismissibleProvider, useDismissible } from '@dismissible/react-client';
73
+
74
+ function App() {
75
+ return (
76
+ <DismissibleProvider
77
+ apiUrl="http://localhost:3000"
78
+ userId="user-123"
79
+ >
80
+ <WelcomeBanner />
81
+ </DismissibleProvider>
82
+ );
83
+ }
84
+
85
+ function WelcomeBanner() {
86
+ const { item, dismiss, isLoading } = useDismissible('welcome-banner');
87
+
88
+ if (isLoading) return <div>Loading...</div>;
89
+ if (item?.dismissedAt) return null;
90
+
91
+ return (
92
+ <div>
93
+ <h2>Welcome!</h2>
94
+ <button onClick={() => dismiss()}>Dismiss</button>
95
+ </div>
96
+ );
97
+ }
98
+ ```
99
+
100
+ The React client automatically uses the built-in REST API endpoints, so no additional configuration is needed on the backend.
101
+
102
+ ## Advanced Usage
103
+
104
+ ### Using PostgreSQL Storage
105
+
106
+ To persist dismissible items in a PostgreSQL database, use the `PostgresStorageModule`:
107
+
108
+ ```typescript
109
+ import { Module } from '@nestjs/common';
110
+ import { DismissibleModule } from '@dismissible/nestjs-dismissible';
111
+ import { PostgresStorageModule } from '@dismissible/nestjs-postgres-storage';
112
+
113
+ @Module({
114
+ imports: [
115
+ DismissibleModule.forRoot({
116
+ storage: PostgresStorageModule,
117
+ }),
118
+ ],
119
+ })
120
+ export class AppModule {}
121
+ ```
122
+
123
+ **Prerequisites:**
124
+
125
+ 1. Install the PostgreSQL storage package:
126
+
127
+ ```bash
128
+ npm install @dismissible/nestjs-postgres-storage
129
+ ```
130
+
131
+ 2. Set up your database connection string:
132
+
133
+ ```env
134
+ DISMISSIBLE_POSTGRES_STORAGE_CONNECTION_STRING=postgresql://user:password@localhost:5432/dismissible
135
+ ```
136
+
137
+ 3. Run Prisma migrations (if using Prisma):
138
+ ```bash
139
+ npx prisma migrate dev
140
+ ```
141
+
142
+ The PostgreSQL adapter uses Prisma and automatically handles schema migrations. The storage persists all dismissible items across application restarts.
143
+
144
+ ### Using the Service
145
+
146
+ Instead of using the built-in REST API endpoints, you can inject `DismissibleService` directly into your controllers or other services for more control:
147
+
148
+ ```typescript
149
+ import { Controller, Get, Param, Delete, Post } from '@nestjs/common';
150
+ import { DismissibleService } from '@dismissible/nestjs-dismissible';
151
+
152
+ @Controller('features')
153
+ export class FeaturesController {
154
+ constructor(private readonly dismissibleService: DismissibleService) {}
155
+
156
+ @Get(':userId/items/:itemId')
157
+ async getOrCreateItem(@Param('userId') userId: string, @Param('itemId') itemId: string) {
158
+ const result = await this.dismissibleService.getOrCreate(
159
+ itemId,
160
+ userId,
161
+ undefined, // optional request context
162
+ );
163
+
164
+ return {
165
+ item: result.item,
166
+ wasCreated: result.created,
167
+ };
168
+ }
169
+
170
+ @Delete(':userId/items/:itemId')
171
+ async dismissItem(@Param('userId') userId: string, @Param('itemId') itemId: string) {
172
+ const result = await this.dismissibleService.dismiss(itemId, userId);
173
+ return { item: result.item };
174
+ }
175
+
176
+ @Post(':userId/items/:itemId/restore')
177
+ async restoreItem(@Param('userId') userId: string, @Param('itemId') itemId: string) {
178
+ const result = await this.dismissibleService.restore(itemId, userId);
179
+ return { item: result.item };
180
+ }
181
+ }
182
+ ```
183
+
184
+ ### JWT Authentication
185
+
186
+ Secure your API endpoints using the JWT Auth Hook with any OIDC-compliant identity provider:
187
+
188
+ ```typescript
189
+ import { Module } from '@nestjs/common';
190
+ import { DismissibleModule } from '@dismissible/nestjs-dismissible';
191
+ import { JwtAuthHookModule, JwtAuthHook } from '@dismissible/nestjs-jwt-auth-hook';
192
+
193
+ @Module({
194
+ imports: [
195
+ JwtAuthHookModule.forRoot({
196
+ enabled: true,
197
+ wellKnownUrl: 'https://auth.example.com/.well-known/openid-configuration',
198
+ issuer: 'https://auth.example.com',
199
+ audience: 'my-api',
200
+ }),
201
+ DismissibleModule.forRoot({
202
+ hooks: [JwtAuthHook],
203
+ }),
204
+ ],
205
+ })
206
+ export class AppModule {}
207
+ ```
208
+
209
+ See the [@dismissible/nestjs-jwt-auth-hook](https://www.npmjs.com/package/@dismissible/nestjs-jwt-auth-hook) package for detailed configuration options.
210
+
211
+ ### Custom Lifecycle Hooks
212
+
213
+ Lifecycle hooks allow you to intercept operations and add custom logic, validation, or mutations:
214
+
215
+ ```typescript
216
+ import { Injectable } from '@nestjs/common';
217
+ import { IDismissibleLifecycleHook, IHookResult } from '@dismissible/nestjs-dismissible';
218
+ @Injectable()
219
+ export class AuditHook implements IDismissibleLifecycleHook {
220
+ // Lower priority runs first (default is 0)
221
+ readonly priority = 10;
222
+
223
+ async onBeforeDismiss(
224
+ itemId: string,
225
+ userId: string,
226
+ context?: IRequestContext,
227
+ ): Promise<IHookResult> {
228
+ // Block dismissal of critical items
229
+ if (itemId.startsWith('critical-')) {
230
+ return {
231
+ proceed: false,
232
+ reason: 'Cannot dismiss critical items',
233
+ };
234
+ }
235
+
236
+ // Allow the operation to proceed
237
+ return { proceed: true };
238
+ }
239
+
240
+ async onAfterCreate(
241
+ itemId: string,
242
+ item: DismissibleItemDto,
243
+ userId: string,
244
+ context?: IRequestContext,
245
+ ): Promise<void> {
246
+ // Log item creation for analytics
247
+ console.log(`Item created: ${itemId} for user ${userId}`);
248
+ }
249
+
250
+ // Mutate item ID before operation
251
+ async onBeforeGetOrCreate(
252
+ itemId: string,
253
+ userId: string,
254
+ context?: IRequestContext,
255
+ ): Promise<IHookResult> {
256
+ // Normalize item IDs (e.g., lowercase)
257
+ return {
258
+ proceed: true,
259
+ mutations: {
260
+ id: itemId.toLowerCase(),
261
+ },
262
+ };
263
+ }
264
+ }
265
+ ```
266
+
267
+ Register hooks in your module:
268
+
269
+ ```typescript
270
+ import { DismissibleModule } from '@dismissible/nestjs-dismissible';
271
+ import { AuditHook } from './hooks/audit.hook';
272
+
273
+ @Module({
274
+ imports: [
275
+ DismissibleModule.forRoot({
276
+ hooks: [AuditHook],
277
+ }),
278
+ ],
279
+ })
280
+ export class AppModule {}
281
+ ```
282
+
283
+ ### Listening to Events
284
+
285
+ The library emits events for all operations. Listen to them using NestJS's `EventEmitter2`:
286
+
287
+ ```typescript
288
+ import { Injectable } from '@nestjs/common';
289
+ import { OnEvent } from '@nestjs/event-emitter';
290
+ import {
291
+ ItemCreatedEvent,
292
+ ItemDismissedEvent,
293
+ ItemRestoredEvent,
294
+ ItemRetrievedEvent,
295
+ DismissibleEvents,
296
+ } from '@dismissible/nestjs-dismissible';
297
+
298
+ @Injectable()
299
+ export class AnalyticsService {
300
+ @OnEvent(DismissibleEvents.ITEM_CREATED)
301
+ handleItemCreated(event: ItemCreatedEvent) {
302
+ // Track item creation in analytics
303
+ console.log(`Analytics: Item ${event.id} created for user ${event.userId}`);
304
+ }
305
+
306
+ @OnEvent(DismissibleEvents.ITEM_DISMISSED)
307
+ handleItemDismissed(event: ItemDismissedEvent) {
308
+ // Track dismissals
309
+ console.log(`Analytics: Item ${event.id} dismissed by user ${event.userId}`);
310
+ }
311
+
312
+ @OnEvent(DismissibleEvents.ITEM_RESTORED)
313
+ handleItemRestored(event: ItemRestoredEvent) {
314
+ // Track restorations
315
+ console.log(`Analytics: Item ${event.id} restored by user ${event.userId}`);
316
+ }
317
+ }
318
+ ```
319
+
320
+ ### Custom Logger
321
+
322
+ Provide a custom logger implementation:
323
+
324
+ ```typescript
325
+ import { Injectable } from '@nestjs/common';
326
+ import { IDismissibleLogger } from '@dismissible/nestjs-logger';
327
+ import { DismissibleModule } from '@dismissible/nestjs-dismissible';
328
+
329
+ @Injectable()
330
+ export class CustomLogger implements IDismissibleLogger {
331
+ debug(message: string, context?: any) {
332
+ // Your custom logging logic
333
+ console.log(`[DEBUG] ${message}`, context);
334
+ }
335
+
336
+ info(message: string, context?: any) {
337
+ console.log(`[INFO] ${message}`, context);
338
+ }
339
+
340
+ warn(message: string, context?: any) {
341
+ console.warn(`[WARN] ${message}`, context);
342
+ }
343
+
344
+ error(message: string, context?: any) {
345
+ console.error(`[ERROR] ${message}`, context);
346
+ }
347
+ }
348
+
349
+ @Module({
350
+ imports: [
351
+ DismissibleModule.forRoot({
352
+ logger: CustomLogger,
353
+ }),
354
+ ],
355
+ })
356
+ export class AppModule {}
357
+ ```
358
+
359
+ ## API Reference
360
+
361
+ ### DismissibleService
362
+
363
+ The main service for interacting with dismissible items.
364
+
365
+ #### Methods
366
+
367
+ **`getOrCreate(itemId, userId, context?)`**
368
+
369
+ Retrieves an existing item or creates a new one if it doesn't exist.
370
+
371
+ - `itemId: string` - Unique identifier for the item
372
+ - `userId: string` - User identifier (required)
373
+ - `context?: IRequestContext` - Optional request context for tracing
374
+
375
+ Returns: `Promise<IGetOrCreateServiceResponse>`
376
+
377
+ **`dismiss(itemId, userId, context?)`**
378
+
379
+ Marks an item as dismissed.
380
+
381
+ - `itemId: string` - Item identifier
382
+ - `userId: string` - User identifier
383
+ - `context?: IRequestContext` - Optional request context
384
+
385
+ Returns: `Promise<IDismissServiceResponse>`
386
+
387
+ **`restore(itemId, userId, context?)`**
388
+
389
+ Restores a previously dismissed item.
390
+
391
+ - `itemId: string` - Item identifier
392
+ - `userId: string` - User identifier
393
+ - `context?: IRequestContext` - Optional request context
394
+
395
+ Returns: `Promise<IRestoreServiceResponse>`
396
+
397
+ ### Module Configuration
398
+
399
+ ```typescript
400
+ interface IDismissibleModuleOptions {
401
+ // Custom storage module (defaults to in-memory storage)
402
+ storage?: DynamicModule | Type<any>;
403
+
404
+ // Custom logger implementation
405
+ logger?: Type<IDismissibleLogger>;
406
+
407
+ // Lifecycle hooks to register
408
+ hooks?: Type<IDismissibleLifecycleHook>[];
409
+ }
410
+ ```
411
+
412
+ ## Events
413
+
414
+ The library emits the following events:
415
+
416
+ - `DismissibleEvents.ITEM_CREATED` - Emitted when a new item is created
417
+ - `DismissibleEvents.ITEM_RETRIEVED` - Emitted when an existing item is retrieved
418
+ - `DismissibleEvents.ITEM_DISMISSED` - Emitted when an item is dismissed
419
+ - `DismissibleEvents.ITEM_RESTORED` - Emitted when an item is restored
420
+
421
+ All events include:
422
+
423
+ - `id: string` - The item identifier
424
+ - `item: DismissibleItemDto` - The current item state
425
+ - `userId: string` - The user identifier
426
+ - `context?: IRequestContext` - Optional request context
427
+
428
+ Dismiss and restore events also include:
429
+
430
+ - `previousItem: DismissibleItemDto` - The item state before the operation
431
+
432
+ ## Lifecycle Hooks
433
+
434
+ Hooks can implement any of the following methods:
435
+
436
+ - `onBeforeGetOrCreate()` - Called before get-or-create operation
437
+ - `onAfterGetOrCreate()` - Called after get-or-create operation
438
+ - `onBeforeCreate()` - Called before creating a new item
439
+ - `onAfterCreate()` - Called after creating a new item
440
+ - `onBeforeDismiss()` - Called before dismissing an item
441
+ - `onAfterDismiss()` - Called after dismissing an item
442
+ - `onBeforeRestore()` - Called before restoring an item
443
+ - `onAfterRestore()` - Called after restoring an item
444
+
445
+ Hooks can:
446
+
447
+ - **Block operations** by returning `{ proceed: false, reason: string }`
448
+ - **Mutate parameters** by returning `{ proceed: true, mutations: { id?, userId?, context? } }`
449
+ - **Perform side effects** in post-hooks (no return value needed)
450
+
451
+ Hooks are executed in priority order (lower numbers first).
452
+
453
+ ## Storage Adapters
454
+
455
+ ### In-Memory Storage (Default)
456
+
457
+ The default storage adapter stores items in memory. Data is lost on application restart.
458
+
459
+ ### PostgreSQL Storage
460
+
461
+ Use `PostgresStorageModule` for persistent storage. See the [Using PostgreSQL Storage](#using-postgresql-storage) section above.
462
+
463
+ ### Custom Storage Adapter
464
+
465
+ Implement the `IDismissibleStorage` interface to create a custom storage adapter:
466
+
467
+ ```typescript
468
+ import { Injectable } from '@nestjs/common';
469
+ import { IDismissibleStorage } from '@dismissible/nestjs-storage';
470
+ import { DismissibleItemDto } from '@dismissible/nestjs-dismissible-item';
471
+
472
+ @Injectable()
473
+ export class RedisStorageAdapter implements IDismissibleStorage {
474
+ async get(userId: string, itemId: string): Promise<DismissibleItemDto | null> {
475
+ // Your implementation
476
+ }
477
+
478
+ async create(userId: string, item: DismissibleItemDto): Promise<void> {
479
+ // Your implementation
480
+ }
481
+
482
+ async update(userId: string, item: DismissibleItemDto): Promise<void> {
483
+ // Your implementation
484
+ }
485
+ }
486
+ ```
487
+
488
+ ## License
489
+
490
+ MIT
package/package.json ADDED
@@ -0,0 +1,68 @@
1
+ {
2
+ "name": "@dismissible/nestjs-dismissible",
3
+ "version": "0.0.1",
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
+ "files": [
15
+ "src",
16
+ "README.md"
17
+ ],
18
+ "dependencies": {
19
+ "@nestjs/event-emitter": "^3.0.1",
20
+ "@dismissible/nestjs-dismissible-hooks": "^0.0.1",
21
+ "@dismissible/nestjs-dismissible-item": "^0.0.1",
22
+ "@dismissible/nestjs-dismissible-request": "^0.0.1",
23
+ "@dismissible/nestjs-storage": "^0.0.1",
24
+ "@dismissible/nestjs-logger": "^0.0.1",
25
+ "@dismissible/nestjs-validation": "^0.0.1"
26
+ },
27
+ "peerDependencies": {
28
+ "@nestjs/common": "^11.0.0",
29
+ "@nestjs/core": "^11.0.0",
30
+ "@nestjs/swagger": "^11.0.0",
31
+ "class-validator": "^0.14.0",
32
+ "class-transformer": "^0.5.0"
33
+ },
34
+ "peerDependenciesMeta": {
35
+ "@nestjs/common": {
36
+ "optional": false
37
+ },
38
+ "@nestjs/core": {
39
+ "optional": false
40
+ },
41
+ "@nestjs/swagger": {
42
+ "optional": false
43
+ },
44
+ "class-validator": {
45
+ "optional": false
46
+ },
47
+ "class-transformer": {
48
+ "optional": false
49
+ }
50
+ },
51
+ "keywords": [
52
+ "nestjs",
53
+ "dismissible",
54
+ "state-management",
55
+ "feature-flags",
56
+ "dismissals"
57
+ ],
58
+ "author": "",
59
+ "license": "MIT",
60
+ "repository": {
61
+ "type": "git",
62
+ "url": "https://github.com/DismissibleIo/dismissible-api"
63
+ },
64
+ "publishConfig": {
65
+ "access": "public"
66
+ },
67
+ "type": "commonjs"
68
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Response DTO for a dismissible item.
3
+ */
4
+ export declare class DismissibleItemResponseDto {
5
+ itemId: string;
6
+ userId: string;
7
+ createdAt: string;
8
+ dismissedAt?: string;
9
+ }
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DismissibleItemResponseDto = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const swagger_1 = require("@nestjs/swagger");
6
+ /**
7
+ * Response DTO for a dismissible item.
8
+ */
9
+ class DismissibleItemResponseDto {
10
+ }
11
+ exports.DismissibleItemResponseDto = DismissibleItemResponseDto;
12
+ tslib_1.__decorate([
13
+ (0, swagger_1.ApiProperty)({
14
+ description: 'Unique identifier for the item',
15
+ example: 'welcome-banner-v2',
16
+ }),
17
+ tslib_1.__metadata("design:type", String)
18
+ ], DismissibleItemResponseDto.prototype, "itemId", void 0);
19
+ tslib_1.__decorate([
20
+ (0, swagger_1.ApiProperty)({
21
+ description: 'User identifier who created the item',
22
+ example: 'user-123',
23
+ }),
24
+ tslib_1.__metadata("design:type", String)
25
+ ], DismissibleItemResponseDto.prototype, "userId", void 0);
26
+ tslib_1.__decorate([
27
+ (0, swagger_1.ApiProperty)({
28
+ description: 'When the item was created (ISO 8601)',
29
+ example: '2024-01-15T10:30:00.000Z',
30
+ }),
31
+ tslib_1.__metadata("design:type", String)
32
+ ], DismissibleItemResponseDto.prototype, "createdAt", void 0);
33
+ tslib_1.__decorate([
34
+ (0, swagger_1.ApiPropertyOptional)({
35
+ description: 'When the item was dismissed (ISO 8601)',
36
+ example: '2024-01-15T12:00:00.000Z',
37
+ }),
38
+ tslib_1.__metadata("design:type", String)
39
+ ], DismissibleItemResponseDto.prototype, "dismissedAt", void 0);
40
+ //# sourceMappingURL=dismissible-item-response.dto.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dismissible-item-response.dto.js","sourceRoot":"","sources":["../../../../../libs/dismissible/src/api/dismissible-item-response.dto.ts"],"names":[],"mappings":";;;;AAAA,6CAAmE;AAEnE;;GAEG;AACH,MAAa,0BAA0B;CAwBtC;AAxBD,gEAwBC;AAnBC;IAJC,IAAA,qBAAW,EAAC;QACX,WAAW,EAAE,gCAAgC;QAC7C,OAAO,EAAE,mBAAmB;KAC7B,CAAC;;0DACc;AAMhB;IAJC,IAAA,qBAAW,EAAC;QACX,WAAW,EAAE,sCAAsC;QACnD,OAAO,EAAE,UAAU;KACpB,CAAC;;0DACc;AAMhB;IAJC,IAAA,qBAAW,EAAC;QACX,WAAW,EAAE,sCAAsC;QACnD,OAAO,EAAE,0BAA0B;KACpC,CAAC;;6DACiB;AAMnB;IAJC,IAAA,6BAAmB,EAAC;QACnB,WAAW,EAAE,wCAAwC;QACrD,OAAO,EAAE,0BAA0B;KACpC,CAAC;;+DACmB"}
@@ -0,0 +1,12 @@
1
+ import { DismissibleItemDto } from '@dismissible/nestjs-dismissible-item';
2
+ import { DismissibleItemResponseDto } from './dismissible-item-response.dto';
3
+ /**
4
+ * Mapper for converting domain objects to DTOs.
5
+ */
6
+ export declare class DismissibleItemMapper {
7
+ /**
8
+ * Convert a dismissible item to a response DTO.
9
+ * Converts Date objects to ISO 8601 strings.
10
+ */
11
+ toResponseDto(item: DismissibleItemDto): DismissibleItemResponseDto;
12
+ }
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DismissibleItemMapper = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const common_1 = require("@nestjs/common");
6
+ const dismissible_item_response_dto_1 = require("./dismissible-item-response.dto");
7
+ /**
8
+ * Mapper for converting domain objects to DTOs.
9
+ */
10
+ let DismissibleItemMapper = class DismissibleItemMapper {
11
+ /**
12
+ * Convert a dismissible item to a response DTO.
13
+ * Converts Date objects to ISO 8601 strings.
14
+ */
15
+ toResponseDto(item) {
16
+ const dto = new dismissible_item_response_dto_1.DismissibleItemResponseDto();
17
+ dto.itemId = item.id;
18
+ dto.userId = item.userId;
19
+ dto.createdAt = item.createdAt.toISOString();
20
+ if (item.dismissedAt) {
21
+ dto.dismissedAt = item.dismissedAt.toISOString();
22
+ }
23
+ return dto;
24
+ }
25
+ };
26
+ exports.DismissibleItemMapper = DismissibleItemMapper;
27
+ exports.DismissibleItemMapper = DismissibleItemMapper = tslib_1.__decorate([
28
+ (0, common_1.Injectable)()
29
+ ], DismissibleItemMapper);
30
+ //# sourceMappingURL=dismissible-item.mapper.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dismissible-item.mapper.js","sourceRoot":"","sources":["../../../../../libs/dismissible/src/api/dismissible-item.mapper.ts"],"names":[],"mappings":";;;;AAAA,2CAA4C;AAE5C,mFAA6E;AAE7E;;GAEG;AAEI,IAAM,qBAAqB,GAA3B,MAAM,qBAAqB;IAChC;;;OAGG;IACH,aAAa,CAAC,IAAwB;QACpC,MAAM,GAAG,GAAG,IAAI,0DAA0B,EAAE,CAAC;QAE7C,GAAG,CAAC,MAAM,GAAG,IAAI,CAAC,EAAE,CAAC;QACrB,GAAG,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;QACzB,GAAG,CAAC,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC,WAAW,EAAE,CAAC;QAE7C,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC;YACrB,GAAG,CAAC,WAAW,GAAG,IAAI,CAAC,WAAW,CAAC,WAAW,EAAE,CAAC;QACnD,CAAC;QAED,OAAO,GAAG,CAAC;IACb,CAAC;CACF,CAAA;AAlBY,sDAAqB;gCAArB,qBAAqB;IADjC,IAAA,mBAAU,GAAE;GACA,qBAAqB,CAkBjC"}
@@ -0,0 +1 @@
1
+ export {};