@dismissible/nestjs-core 1.0.3-alpha.cdc64a4.0 → 2.0.2-alpha.72996f5.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 (77) hide show
  1. package/README.md +435 -233
  2. package/package.json +7 -7
  3. package/src/api/api.constants.d.ts +4 -0
  4. package/src/api/api.constants.js +8 -0
  5. package/src/api/api.constants.js.map +1 -0
  6. package/src/api/dismissible-item.mapper.d.ts +2 -1
  7. package/src/api/dismissible-item.mapper.interface.d.ts +17 -0
  8. package/src/api/dismissible-item.mapper.interface.js +8 -0
  9. package/src/api/dismissible-item.mapper.interface.js.map +1 -0
  10. package/src/api/dismissible-item.mapper.js.map +1 -1
  11. package/src/api/index.d.ts +2 -0
  12. package/src/api/index.js +2 -0
  13. package/src/api/index.js.map +1 -1
  14. package/src/api/use-cases/dismiss/dismiss.controller.d.ts +4 -4
  15. package/src/api/use-cases/dismiss/dismiss.controller.js +9 -7
  16. package/src/api/use-cases/dismiss/dismiss.controller.js.map +1 -1
  17. package/src/api/use-cases/dismiss/dismiss.controller.spec.js.map +1 -1
  18. package/src/api/use-cases/get-or-create/get-or-create.controller.d.ts +4 -4
  19. package/src/api/use-cases/get-or-create/get-or-create.controller.js +9 -7
  20. package/src/api/use-cases/get-or-create/get-or-create.controller.js.map +1 -1
  21. package/src/api/use-cases/restore/restore.controller.d.ts +4 -4
  22. package/src/api/use-cases/restore/restore.controller.js +9 -7
  23. package/src/api/use-cases/restore/restore.controller.js.map +1 -1
  24. package/src/api/use-cases/restore/restore.controller.spec.js.map +1 -1
  25. package/src/core/dismissible-core.service.d.ts +7 -6
  26. package/src/core/dismissible-core.service.interface.d.ts +48 -0
  27. package/src/core/dismissible-core.service.interface.js +8 -0
  28. package/src/core/dismissible-core.service.interface.js.map +1 -0
  29. package/src/core/dismissible-core.service.js +10 -8
  30. package/src/core/dismissible-core.service.js.map +1 -1
  31. package/src/core/dismissible-core.service.spec.js +1 -2
  32. package/src/core/dismissible-core.service.spec.js.map +1 -1
  33. package/src/core/dismissible.service.d.ts +6 -5
  34. package/src/core/dismissible.service.interface.d.ts +33 -0
  35. package/src/core/dismissible.service.interface.js +8 -0
  36. package/src/core/dismissible.service.interface.js.map +1 -0
  37. package/src/core/dismissible.service.js +6 -4
  38. package/src/core/dismissible.service.js.map +1 -1
  39. package/src/core/dismissible.service.spec.js +1 -2
  40. package/src/core/dismissible.service.spec.js.map +1 -1
  41. package/src/core/hook-runner.interface.d.ts +56 -0
  42. package/src/core/hook-runner.interface.js +8 -0
  43. package/src/core/hook-runner.interface.js.map +1 -0
  44. package/src/core/hook-runner.service.d.ts +2 -1
  45. package/src/core/hook-runner.service.js.map +1 -1
  46. package/src/core/hook-runner.service.spec.js.map +1 -1
  47. package/src/core/index.d.ts +3 -0
  48. package/src/core/index.js +3 -0
  49. package/src/core/index.js.map +1 -1
  50. package/src/dismissible.module.d.ts +4 -1
  51. package/src/dismissible.module.js +39 -2
  52. package/src/dismissible.module.js.map +1 -1
  53. package/src/response/http-exception-filter.spec.js.map +1 -1
  54. package/src/response/index.d.ts +1 -0
  55. package/src/response/index.js +1 -0
  56. package/src/response/index.js.map +1 -1
  57. package/src/response/response.service.d.ts +2 -1
  58. package/src/response/response.service.interface.d.ts +19 -0
  59. package/src/response/response.service.interface.js +8 -0
  60. package/src/response/response.service.interface.js.map +1 -0
  61. package/src/response/response.service.js.map +1 -1
  62. package/src/utils/date/date.service.d.ts +2 -1
  63. package/src/utils/date/date.service.interface.d.ts +21 -0
  64. package/src/utils/date/date.service.interface.js +8 -0
  65. package/src/utils/date/date.service.interface.js.map +1 -0
  66. package/src/utils/date/date.service.js.map +1 -1
  67. package/src/utils/date/index.d.ts +1 -0
  68. package/src/utils/date/index.js +1 -0
  69. package/src/utils/date/index.js.map +1 -1
  70. package/src/utils/dismissible.helper.d.ts +2 -1
  71. package/src/utils/dismissible.helper.interface.d.ts +14 -0
  72. package/src/utils/dismissible.helper.interface.js +8 -0
  73. package/src/utils/dismissible.helper.interface.js.map +1 -0
  74. package/src/utils/dismissible.helper.js.map +1 -1
  75. package/src/utils/index.d.ts +1 -0
  76. package/src/utils/index.js +1 -0
  77. package/src/utils/index.js.map +1 -1
package/README.md CHANGED
@@ -1,5 +1,5 @@
1
1
  <p align="center">
2
- <a href="https://dismissible.io" target="_blank"><img src="../../docs/images/dismissible_logo.png" width="240" alt="Dismissible" /></a>
2
+ <a href="https://dismissible.io" target="_blank"><img src="https://raw.githubusercontent.com/DismissibleIo/dismissible-api/main/docs/images/dismissible_logo.png" width="240" alt="Dismissible" /></a>
3
3
  </p>
4
4
 
5
5
  <p align="center">Never Show The Same Thing Twice!</p>
@@ -14,10 +14,28 @@
14
14
 
15
15
  # @dismissible/nestjs-core
16
16
 
17
- A powerful NestJS library for managing dismissible state in your applications. Perfect for guided tours, user preferences, onboarding flows, and any scenario where you need to track whether a user has dismissed or interacted with specific items.
17
+ The core NestJS library for managing dismissible state in your applications. This is the central package that ties together all the main Dismissible libraries and contains the domain logic for dismissible items.
18
+
19
+ Perfect for guided tours, user preferences, onboarding flows, and any scenario where you need to track whether a user has dismissed or interacted with specific items.
18
20
 
19
21
  > **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.
20
22
 
23
+ ## Related Packages
24
+
25
+ This core library integrates with several lower-level packages:
26
+
27
+ | Package | Description |
28
+ | ------------------------------------------------------------------------------------------------------------ | -------------------------------------------- |
29
+ | [@dismissible/nestjs-storage](https://www.npmjs.com/package/@dismissible/nestjs-storage) | Storage interfaces and base module |
30
+ | [@dismissible/nestjs-postgres-storage](https://www.npmjs.com/package/@dismissible/nestjs-postgres-storage) | PostgreSQL storage adapter |
31
+ | [@dismissible/nestjs-dynamodb-storage](https://www.npmjs.com/package/@dismissible/nestjs-dynamodb-storage) | DynamoDB storage adapter |
32
+ | [@dismissible/nestjs-hooks](https://www.npmjs.com/package/@dismissible/nestjs-hooks) | Lifecycle hook interfaces |
33
+ | [@dismissible/nestjs-jwt-auth-hook](https://www.npmjs.com/package/@dismissible/nestjs-jwt-auth-hook) | JWT authentication hook |
34
+ | [@dismissible/nestjs-rate-limiter-hook](https://www.npmjs.com/package/@dismissible/nestjs-rate-limiter-hook) | Rate limiting hook |
35
+ | [@dismissible/nestjs-logger](https://www.npmjs.com/package/@dismissible/nestjs-logger) | Logger interfaces and default implementation |
36
+ | [@dismissible/nestjs-item](https://www.npmjs.com/package/@dismissible/nestjs-item) | Dismissible item DTOs and types |
37
+ | [@dismissible/react-client](https://www.npmjs.com/package/@dismissible/react-client) | React client for frontend integration |
38
+
21
39
  ## Features
22
40
 
23
41
  - **Simple API** - Easy-to-use service methods for get-or-create, dismiss, and restore operations
@@ -48,7 +66,7 @@ import { Module } from '@nestjs/common';
48
66
  import { DismissibleModule } from '@dismissible/nestjs-core';
49
67
 
50
68
  @Module({
51
- imports: [DismissibleModule.forRoot({})],
69
+ imports: [DismissibleModule.forRoot()],
52
70
  })
53
71
  export class AppModule {}
54
72
  ```
@@ -113,11 +131,39 @@ function WelcomeBanner() {
113
131
 
114
132
  The React client automatically uses the built-in REST API endpoints, so no additional configuration is needed on the backend.
115
133
 
116
- ## Advanced Usage
134
+ ## Configuration Options
135
+
136
+ All configuration is done through `DismissibleModule.forRoot()`. The following options are available:
137
+
138
+ ```typescript
139
+ interface IDismissibleModuleOptions {
140
+ // Custom storage module (defaults to memory storage)
141
+ storage?: Type<any> | DynamicModule;
142
+
143
+ // Custom logger implementation
144
+ logger?: Type<IDismissibleLogger>;
145
+
146
+ // Lifecycle hooks to register
147
+ hooks?: Type<IDismissibleLifecycleHook>[];
148
+
149
+ // Additional modules to import
150
+ imports?: DynamicModule[];
151
+
152
+ // Additional providers to register
153
+ providers?: Provider[];
154
+
155
+ // Custom controllers (overrides default REST API controllers)
156
+ controllers?: Type<any>[];
157
+ }
158
+ ```
159
+
160
+ ---
117
161
 
118
- ### Using PostgreSQL Storage
162
+ ### `storage`
119
163
 
120
- To persist dismissible items in a PostgreSQL database, use the `PostgresStorageModule`:
164
+ Specifies a custom storage module for persisting dismissible items. Defaults to in-memory storage.
165
+
166
+ **Default:** In-memory storage (data lost on restart)
121
167
 
122
168
  ```typescript
123
169
  import { Module } from '@nestjs/common';
@@ -127,77 +173,100 @@ import { PostgresStorageModule } from '@dismissible/nestjs-postgres-storage';
127
173
  @Module({
128
174
  imports: [
129
175
  DismissibleModule.forRoot({
130
- storage: PostgresStorageModule,
176
+ storage: PostgresStorageModule.forRoot({
177
+ connectionString: 'postgresql://user:password@localhost:5432/dismissible',
178
+ }),
131
179
  }),
132
180
  ],
133
181
  })
134
182
  export class AppModule {}
135
183
  ```
136
184
 
137
- **Prerequisites:**
138
-
139
- 1. Install the PostgreSQL storage package:
185
+ **Related packages:**
140
186
 
141
- ```bash
142
- npm install @dismissible/nestjs-postgres-storage
143
- ```
187
+ - [@dismissible/nestjs-storage](https://www.npmjs.com/package/@dismissible/nestjs-storage) - Storage interfaces and base module for implementing custom adapters
188
+ - [@dismissible/nestjs-postgres-storage](https://www.npmjs.com/package/@dismissible/nestjs-postgres-storage) - PostgreSQL storage adapter
189
+ - [@dismissible/nestjs-dynamodb-storage](https://www.npmjs.com/package/@dismissible/nestjs-dynamodb-storage) - DynamoDB storage adapter
144
190
 
145
- 2. Set up your database connection string:
191
+ ---
146
192
 
147
- ```env
148
- DISMISSIBLE_STORAGE_POSTGRES_CONNECTION_STRING=postgresql://user:password@localhost:5432/dismissible
149
- ```
193
+ ### `logger`
150
194
 
151
- 3. Run Prisma migrations (if using Prisma):
152
- ```bash
153
- npx prisma migrate dev
154
- ```
195
+ Provides a custom logger implementation. The logger must implement the `IDismissibleLogger` interface.
155
196
 
156
- The PostgreSQL adapter uses Prisma and automatically handles schema migrations. The storage persists all dismissible items across application restarts.
157
-
158
- ### Using the Service
159
-
160
- Instead of using the built-in REST API endpoints, you can inject `DismissibleService` directly into your controllers or other services for more control:
197
+ **Default:** Built-in console logger
161
198
 
162
199
  ```typescript
163
- import { Controller, Get, Param, Delete, Post } from '@nestjs/common';
164
- import { DismissibleService } from '@dismissible/nestjs-core';
165
-
166
- @Controller('features')
167
- export class FeaturesController {
168
- constructor(private readonly dismissibleService: DismissibleService) {}
200
+ import { Injectable, Module } from '@nestjs/common';
201
+ import { DismissibleModule } from '@dismissible/nestjs-core';
202
+ import { IDismissibleLogger } from '@dismissible/nestjs-logger';
169
203
 
170
- @Get(':userId/items/:itemId')
171
- async getOrCreateItem(@Param('userId') userId: string, @Param('itemId') itemId: string) {
172
- const result = await this.dismissibleService.getOrCreate(
173
- itemId,
174
- userId,
175
- undefined, // optional request context
176
- );
204
+ @Injectable()
205
+ class CustomLogger implements IDismissibleLogger {
206
+ debug(message: string, context?: any) {
207
+ console.log(`[DEBUG] ${message}`, context);
208
+ }
177
209
 
178
- return {
179
- item: result.item,
180
- wasCreated: result.created,
181
- };
210
+ info(message: string, context?: any) {
211
+ console.log(`[INFO] ${message}`, context);
182
212
  }
183
213
 
184
- @Delete(':userId/items/:itemId')
185
- async dismissItem(@Param('userId') userId: string, @Param('itemId') itemId: string) {
186
- const result = await this.dismissibleService.dismiss(itemId, userId);
187
- return { item: result.item };
214
+ warn(message: string, context?: any) {
215
+ console.warn(`[WARN] ${message}`, context);
188
216
  }
189
217
 
190
- @Post(':userId/items/:itemId/restore')
191
- async restoreItem(@Param('userId') userId: string, @Param('itemId') itemId: string) {
192
- const result = await this.dismissibleService.restore(itemId, userId);
193
- return { item: result.item };
218
+ error(message: string, context?: any) {
219
+ console.error(`[ERROR] ${message}`, context);
194
220
  }
195
221
  }
222
+
223
+ @Module({
224
+ imports: [
225
+ DismissibleModule.forRoot({
226
+ logger: CustomLogger,
227
+ }),
228
+ ],
229
+ })
230
+ export class AppModule {}
196
231
  ```
197
232
 
198
- ### JWT Authentication
233
+ **Related packages:**
234
+
235
+ - [@dismissible/nestjs-logger](https://www.npmjs.com/package/@dismissible/nestjs-logger) - Logger interfaces and default implementation
236
+
237
+ ---
238
+
239
+ ### `hooks`
240
+
241
+ Registers lifecycle hooks that intercept operations. Hooks can block operations, mutate parameters, or perform side effects.
242
+
243
+ **Default:** No hooks
244
+
245
+ ```typescript
246
+ import { Injectable, Module } from '@nestjs/common';
247
+ import { DismissibleModule } from '@dismissible/nestjs-core';
248
+ import { IDismissibleLifecycleHook, IHookResult } from '@dismissible/nestjs-hooks';
249
+
250
+ @Injectable()
251
+ class AuditHook implements IDismissibleLifecycleHook {
252
+ readonly priority = 10; // Lower runs first
253
+
254
+ async onAfterDismiss(itemId: string, userId: string): Promise<void> {
255
+ console.log(`User ${userId} dismissed ${itemId}`);
256
+ }
257
+ }
258
+
259
+ @Module({
260
+ imports: [
261
+ DismissibleModule.forRoot({
262
+ hooks: [AuditHook],
263
+ }),
264
+ ],
265
+ })
266
+ export class AppModule {}
267
+ ```
199
268
 
200
- Secure your API endpoints using the JWT Auth Hook with any OIDC-compliant identity provider:
269
+ For JWT authentication, use the [@dismissible/nestjs-jwt-auth-hook](https://www.npmjs.com/package/@dismissible/nestjs-jwt-auth-hook) package:
201
270
 
202
271
  ```typescript
203
272
  import { Module } from '@nestjs/common';
@@ -220,83 +289,170 @@ import { JwtAuthHookModule, JwtAuthHook } from '@dismissible/nestjs-jwt-auth-hoo
220
289
  export class AppModule {}
221
290
  ```
222
291
 
223
- See the [@dismissible/nestjs-jwt-auth-hook](https://www.npmjs.com/package/@dismissible/nestjs-jwt-auth-hook) package for detailed configuration options.
292
+ **Related packages:**
293
+
294
+ - [@dismissible/nestjs-hooks](https://www.npmjs.com/package/@dismissible/nestjs-hooks) - Hook interfaces and types for implementing custom lifecycle hooks
295
+ - [@dismissible/nestjs-jwt-auth-hook](https://www.npmjs.com/package/@dismissible/nestjs-jwt-auth-hook) - JWT authentication hook for OIDC providers
296
+ - [@dismissible/nestjs-rate-limiter-hook](https://www.npmjs.com/package/@dismissible/nestjs-rate-limiter-hook) - Rate limiting hook
297
+
298
+ ---
299
+
300
+ ### `imports`
224
301
 
225
- ### Custom Lifecycle Hooks
302
+ Adds additional modules to the DismissibleModule's imports. Useful for injecting dependencies that your hooks or providers need.
226
303
 
227
- Lifecycle hooks allow you to intercept operations and add custom logic, validation, or mutations:
304
+ **Default:** None
228
305
 
229
306
  ```typescript
230
- import { Injectable } from '@nestjs/common';
231
- import { IDismissibleLifecycleHook, IHookResult } from '@dismissible/nestjs-core';
232
- @Injectable()
233
- export class AuditHook implements IDismissibleLifecycleHook {
234
- // Lower priority runs first (default is 0)
235
- readonly priority = 10;
307
+ import { Module } from '@nestjs/common';
308
+ import { DismissibleModule } from '@dismissible/nestjs-core';
309
+ import { HttpModule } from '@nestjs/axios';
236
310
 
237
- async onBeforeDismiss(
238
- itemId: string,
239
- userId: string,
240
- context?: IRequestContext,
241
- ): Promise<IHookResult> {
242
- // Block dismissal of critical items
243
- if (itemId.startsWith('critical-')) {
244
- return {
245
- proceed: false,
246
- reason: 'Cannot dismiss critical items',
247
- };
248
- }
249
-
250
- // Allow the operation to proceed
251
- return { proceed: true };
252
- }
311
+ @Module({
312
+ imports: [
313
+ DismissibleModule.forRoot({
314
+ imports: [HttpModule],
315
+ hooks: [WebhookHook], // Hook that uses HttpService
316
+ }),
317
+ ],
318
+ })
319
+ export class AppModule {}
320
+ ```
253
321
 
254
- async onAfterCreate(
255
- itemId: string,
256
- item: DismissibleItemDto,
257
- userId: string,
258
- context?: IRequestContext,
259
- ): Promise<void> {
260
- // Log item creation for analytics
261
- console.log(`Item created: ${itemId} for user ${userId}`);
322
+ ---
323
+
324
+ ### `providers`
325
+
326
+ Registers additional providers within the DismissibleModule. Useful for services that your hooks depend on.
327
+
328
+ **Default:** None
329
+
330
+ ```typescript
331
+ import { Injectable, Module } from '@nestjs/common';
332
+ import { DismissibleModule, IDismissibleLifecycleHook } from '@dismissible/nestjs-core';
333
+
334
+ @Injectable()
335
+ class AnalyticsService {
336
+ track(event: string, data: any) {
337
+ // Send to analytics
262
338
  }
339
+ }
263
340
 
264
- // Mutate item ID before operation
265
- async onBeforeGetOrCreate(
266
- itemId: string,
267
- userId: string,
268
- context?: IRequestContext,
269
- ): Promise<IHookResult> {
270
- // Normalize item IDs (e.g., lowercase)
271
- return {
272
- proceed: true,
273
- mutations: {
274
- id: itemId.toLowerCase(),
275
- },
276
- };
341
+ @Injectable()
342
+ class AnalyticsHook implements IDismissibleLifecycleHook {
343
+ constructor(private analytics: AnalyticsService) {}
344
+
345
+ async onAfterDismiss(itemId: string, userId: string): Promise<void> {
346
+ this.analytics.track('item_dismissed', { itemId, userId });
277
347
  }
278
348
  }
349
+
350
+ @Module({
351
+ imports: [
352
+ DismissibleModule.forRoot({
353
+ providers: [AnalyticsService],
354
+ hooks: [AnalyticsHook],
355
+ }),
356
+ ],
357
+ })
358
+ export class AppModule {}
279
359
  ```
280
360
 
281
- Register hooks in your module:
361
+ ---
362
+
363
+ ### `controllers`
364
+
365
+ Overrides the default REST API controllers. Use this when you want complete control over the API endpoints.
366
+
367
+ **Default:** Built-in controllers for get-or-create, dismiss, and restore
282
368
 
283
369
  ```typescript
284
- import { DismissibleModule } from '@dismissible/nestjs-core';
285
- import { AuditHook } from './hooks/audit.hook';
370
+ import { Controller, Get, Param, Module, Inject } from '@nestjs/common';
371
+ import {
372
+ DismissibleModule,
373
+ IDismissibleService,
374
+ DISMISSIBLE_SERVICE,
375
+ } from '@dismissible/nestjs-core';
376
+
377
+ @Controller('custom')
378
+ class CustomController {
379
+ constructor(
380
+ @Inject(DISMISSIBLE_SERVICE)
381
+ private dismissibleService: IDismissibleService,
382
+ ) {}
383
+
384
+ @Get(':userId/:itemId')
385
+ async getItem(@Param('userId') userId: string, @Param('itemId') itemId: string) {
386
+ return this.dismissibleService.getOrCreate(itemId, userId);
387
+ }
388
+ }
286
389
 
287
390
  @Module({
288
391
  imports: [
289
392
  DismissibleModule.forRoot({
290
- hooks: [AuditHook],
393
+ controllers: [CustomController],
291
394
  }),
292
395
  ],
293
396
  })
294
397
  export class AppModule {}
295
398
  ```
296
399
 
297
- ### Listening to Events
400
+ To disable the REST API entirely, pass an empty array:
298
401
 
299
- The library emits events for all operations. Listen to them using NestJS's `EventEmitter2`:
402
+ ```typescript
403
+ @Module({
404
+ imports: [
405
+ DismissibleModule.forRoot({
406
+ controllers: [],
407
+ }),
408
+ ],
409
+ })
410
+ export class AppModule {}
411
+ ```
412
+
413
+ ---
414
+
415
+ ## Using the Service Directly
416
+
417
+ Instead of using the built-in REST API, you can inject `DismissibleService` into your own controllers or services:
418
+
419
+ ```typescript
420
+ import { Controller, Get, Param, Delete, Post, Inject } from '@nestjs/common';
421
+ import { IDismissibleService, DISMISSIBLE_SERVICE } from '@dismissible/nestjs-core';
422
+
423
+ @Controller('features')
424
+ export class FeaturesController {
425
+ constructor(
426
+ @Inject(DISMISSIBLE_SERVICE)
427
+ private readonly dismissibleService: IDismissibleService,
428
+ ) {}
429
+
430
+ @Get(':userId/items/:itemId')
431
+ async getOrCreateItem(@Param('userId') userId: string, @Param('itemId') itemId: string) {
432
+ const result = await this.dismissibleService.getOrCreate(itemId, userId);
433
+ return {
434
+ item: result.item,
435
+ wasCreated: result.created,
436
+ };
437
+ }
438
+
439
+ @Delete(':userId/items/:itemId')
440
+ async dismissItem(@Param('userId') userId: string, @Param('itemId') itemId: string) {
441
+ const result = await this.dismissibleService.dismiss(itemId, userId);
442
+ return { item: result.item };
443
+ }
444
+
445
+ @Post(':userId/items/:itemId/restore')
446
+ async restoreItem(@Param('userId') userId: string, @Param('itemId') itemId: string) {
447
+ const result = await this.dismissibleService.restore(itemId, userId);
448
+ return { item: result.item };
449
+ }
450
+ }
451
+ ```
452
+
453
+ ## Events
454
+
455
+ The library emits events for all operations using NestJS's EventEmitter2:
300
456
 
301
457
  ```typescript
302
458
  import { Injectable } from '@nestjs/common';
@@ -305,7 +461,6 @@ import {
305
461
  ItemCreatedEvent,
306
462
  ItemDismissedEvent,
307
463
  ItemRestoredEvent,
308
- ItemRetrievedEvent,
309
464
  DismissibleEvents,
310
465
  } from '@dismissible/nestjs-core';
311
466
 
@@ -313,188 +468,235 @@ import {
313
468
  export class AnalyticsService {
314
469
  @OnEvent(DismissibleEvents.ITEM_CREATED)
315
470
  handleItemCreated(event: ItemCreatedEvent) {
316
- // Track item creation in analytics
317
- console.log(`Analytics: Item ${event.id} created for user ${event.userId}`);
471
+ console.log(`Item ${event.id} created for user ${event.userId}`);
318
472
  }
319
473
 
320
474
  @OnEvent(DismissibleEvents.ITEM_DISMISSED)
321
475
  handleItemDismissed(event: ItemDismissedEvent) {
322
- // Track dismissals
323
- console.log(`Analytics: Item ${event.id} dismissed by user ${event.userId}`);
476
+ console.log(`Item ${event.id} dismissed by user ${event.userId}`);
324
477
  }
325
478
 
326
479
  @OnEvent(DismissibleEvents.ITEM_RESTORED)
327
480
  handleItemRestored(event: ItemRestoredEvent) {
328
- // Track restorations
329
- console.log(`Analytics: Item ${event.id} restored by user ${event.userId}`);
481
+ console.log(`Item ${event.id} restored by user ${event.userId}`);
330
482
  }
331
483
  }
332
484
  ```
333
485
 
334
- ### Custom Logger
486
+ Available events:
487
+
488
+ - `DismissibleEvents.ITEM_CREATED` - New item created
489
+ - `DismissibleEvents.ITEM_RETRIEVED` - Existing item retrieved
490
+ - `DismissibleEvents.ITEM_DISMISSED` - Item dismissed
491
+ - `DismissibleEvents.ITEM_RESTORED` - Item restored
492
+
493
+ ## Overriding Services
494
+
495
+ All core services can be overridden using symbol-based dependency injection tokens. This allows you to provide custom implementations while maintaining type safety.
335
496
 
336
- Provide a custom logger implementation:
497
+ ### Available Service Tokens
498
+
499
+ | Token | Interface | Description |
500
+ | ------------------------------ | ------------------------- | ----------------------------------------------- |
501
+ | `DISMISSIBLE_SERVICE` | `IDismissibleService` | Main orchestration service |
502
+ | `DISMISSIBLE_CORE_SERVICE` | `IDismissibleCoreService` | Core business logic service |
503
+ | `DISMISSIBLE_HOOK_RUNNER` | `IHookRunner` | Lifecycle hook execution |
504
+ | `DISMISSIBLE_HELPER` | `IDismissibleHelper` | Helper utilities |
505
+ | `DISMISSIBLE_DATE_SERVICE` | `IDateService` | Date operations |
506
+ | `DISMISSIBLE_RESPONSE_SERVICE` | `IResponseService` | HTTP response formatting |
507
+ | `DISMISSIBLE_ITEM_MAPPER` | `IDismissibleItemMapper` | Domain to DTO mapping |
508
+ | `DISMISSIBLE_ITEM_FACTORY` | `IDismissibleItemFactory` | Item creation (from `@dismissible/nestjs-item`) |
509
+
510
+ ### Example: Overriding the Date Service
511
+
512
+ Override the date service to control time in tests or add custom behavior:
337
513
 
338
514
  ```typescript
339
- import { Injectable } from '@nestjs/common';
340
- import { IDismissibleLogger } from '@dismissible/nestjs-logger';
341
- import { DismissibleModule } from '@dismissible/nestjs-core';
515
+ import { Injectable, Module } from '@nestjs/common';
516
+ import {
517
+ DismissibleModule,
518
+ IDateService,
519
+ DISMISSIBLE_DATE_SERVICE,
520
+ } from '@dismissible/nestjs-core';
342
521
 
343
522
  @Injectable()
344
- export class CustomLogger implements IDismissibleLogger {
345
- debug(message: string, context?: any) {
346
- // Your custom logging logic
347
- console.log(`[DEBUG] ${message}`, context);
523
+ class CustomDateService implements IDateService {
524
+ getNow(): Date {
525
+ // Custom implementation - e.g., use a fixed time for testing
526
+ return new Date('2024-01-01T00:00:00.000Z');
348
527
  }
349
528
 
350
- info(message: string, context?: any) {
351
- console.log(`[INFO] ${message}`, context);
529
+ parseIso(isoString: string): Date {
530
+ return new Date(isoString);
352
531
  }
353
532
 
354
- warn(message: string, context?: any) {
355
- console.warn(`[WARN] ${message}`, context);
356
- }
357
-
358
- error(message: string, context?: any) {
359
- console.error(`[ERROR] ${message}`, context);
533
+ toIso(date: Date): string {
534
+ return date.toISOString();
360
535
  }
361
536
  }
362
537
 
363
538
  @Module({
364
539
  imports: [
365
540
  DismissibleModule.forRoot({
366
- logger: CustomLogger,
541
+ providers: [
542
+ CustomDateService,
543
+ { provide: DISMISSIBLE_DATE_SERVICE, useExisting: CustomDateService },
544
+ ],
367
545
  }),
368
546
  ],
369
547
  })
370
548
  export class AppModule {}
371
549
  ```
372
550
 
373
- ## API Reference
374
-
375
- ### DismissibleService
376
-
377
- The main service for interacting with dismissible items.
378
-
379
- #### Methods
380
-
381
- **`getOrCreate(itemId, userId, context?)`**
382
-
383
- Retrieves an existing item or creates a new one if it doesn't exist.
384
-
385
- - `itemId: string` - Unique identifier for the item
386
- - `userId: string` - User identifier (required)
387
- - `context?: IRequestContext` - Optional request context for tracing
388
-
389
- Returns: `Promise<IGetOrCreateServiceResponse>`
551
+ ### Example: Overriding the Main Service
390
552
 
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>`
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>`
410
-
411
- ### Module Configuration
553
+ Override the main dismissible service for custom orchestration logic:
412
554
 
413
555
  ```typescript
414
- interface IDismissibleModuleOptions {
415
- // Custom storage module (defaults to 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>[];
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` - The current item state
439
- - `userId: string` - The user identifier
440
- - `context?: IRequestContext` - Optional request context
556
+ import { Injectable, Inject, Module } from '@nestjs/common';
557
+ import {
558
+ DismissibleModule,
559
+ IDismissibleService,
560
+ IDismissibleCoreService,
561
+ DISMISSIBLE_SERVICE,
562
+ DISMISSIBLE_CORE_SERVICE,
563
+ IGetOrCreateServiceResponse,
564
+ IDismissServiceResponse,
565
+ IRestoreServiceResponse,
566
+ } from '@dismissible/nestjs-core';
567
+ import { IRequestContext } from '@dismissible/nestjs-request';
441
568
 
442
- Dismiss and restore events also include:
569
+ @Injectable()
570
+ class CustomDismissibleService implements IDismissibleService {
571
+ constructor(
572
+ @Inject(DISMISSIBLE_CORE_SERVICE)
573
+ private readonly coreService: IDismissibleCoreService,
574
+ ) {}
443
575
 
444
- - `previousItem: DismissibleItemDto` - The item state before the operation
576
+ async getOrCreate(
577
+ itemId: string,
578
+ userId: string,
579
+ context?: IRequestContext,
580
+ ): Promise<IGetOrCreateServiceResponse> {
581
+ // Add custom logic before/after core operation
582
+ console.log('Custom getOrCreate called');
583
+ return this.coreService.getOrCreate(itemId, userId);
584
+ }
445
585
 
446
- ## Lifecycle Hooks
586
+ async dismiss(
587
+ itemId: string,
588
+ userId: string,
589
+ context?: IRequestContext,
590
+ ): Promise<IDismissServiceResponse> {
591
+ return this.coreService.dismiss(itemId, userId);
592
+ }
447
593
 
448
- Hooks can implement any of the following methods:
594
+ async restore(
595
+ itemId: string,
596
+ userId: string,
597
+ context?: IRequestContext,
598
+ ): Promise<IRestoreServiceResponse> {
599
+ return this.coreService.restore(itemId, userId);
600
+ }
601
+ }
449
602
 
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
603
+ @Module({
604
+ imports: [
605
+ DismissibleModule.forRoot({
606
+ providers: [
607
+ CustomDismissibleService,
608
+ { provide: DISMISSIBLE_SERVICE, useExisting: CustomDismissibleService },
609
+ ],
610
+ }),
611
+ ],
612
+ })
613
+ export class AppModule {}
614
+ ```
458
615
 
459
- Hooks can:
616
+ ### Example: Overriding the Item Factory
460
617
 
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)
618
+ Override the item factory to customize how items are created:
464
619
 
465
- Hooks are executed in priority order (lower numbers first).
620
+ ```typescript
621
+ import { Injectable, Module } from '@nestjs/common';
622
+ import { DismissibleModule } from '@dismissible/nestjs-core';
623
+ import {
624
+ DismissibleItemDto,
625
+ IDismissibleItemFactory,
626
+ ICreateDismissibleItemOptions,
627
+ DISMISSIBLE_ITEM_FACTORY,
628
+ } from '@dismissible/nestjs-item';
466
629
 
467
- ## Storage Adapters
630
+ @Injectable()
631
+ class CustomItemFactory implements IDismissibleItemFactory {
632
+ create(options: ICreateDismissibleItemOptions): DismissibleItemDto {
633
+ const item = new DismissibleItemDto();
634
+ item.id = options.id;
635
+ item.userId = options.userId;
636
+ item.createdAt = options.createdAt;
637
+ item.dismissedAt = options.dismissedAt;
638
+ return item;
639
+ }
468
640
 
469
- ### In-Memory Storage (Default)
641
+ clone(item: DismissibleItemDto): DismissibleItemDto {
642
+ return this.create({
643
+ id: item.id,
644
+ createdAt: item.createdAt,
645
+ userId: item.userId,
646
+ dismissedAt: item.dismissedAt,
647
+ });
648
+ }
470
649
 
471
- The default storage adapter stores items in memory. Data is lost on application restart.
650
+ createDismissed(item: DismissibleItemDto, dismissedAt: Date): DismissibleItemDto {
651
+ return this.create({
652
+ id: item.id,
653
+ createdAt: item.createdAt,
654
+ userId: item.userId,
655
+ dismissedAt,
656
+ });
657
+ }
472
658
 
473
- ### PostgreSQL Storage
659
+ createRestored(item: DismissibleItemDto): DismissibleItemDto {
660
+ return this.create({
661
+ id: item.id,
662
+ createdAt: item.createdAt,
663
+ userId: item.userId,
664
+ dismissedAt: undefined,
665
+ });
666
+ }
667
+ }
474
668
 
475
- Use `PostgresStorageModule` for persistent storage. See the [Using PostgreSQL Storage](#using-postgresql-storage) section above.
669
+ @Module({
670
+ imports: [
671
+ DismissibleModule.forRoot({
672
+ providers: [
673
+ CustomItemFactory,
674
+ { provide: DISMISSIBLE_ITEM_FACTORY, useExisting: CustomItemFactory },
675
+ ],
676
+ }),
677
+ ],
678
+ })
679
+ export class AppModule {}
680
+ ```
476
681
 
477
- ### Custom Storage Adapter
682
+ ### Injecting Overridable Services
478
683
 
479
- Implement the `IDismissibleStorage` interface to create a custom storage adapter:
684
+ When injecting these services in your own code, use the symbol tokens for maximum flexibility:
480
685
 
481
686
  ```typescript
482
- import { Injectable } from '@nestjs/common';
483
- import { IDismissibleStorage } from '@dismissible/nestjs-storage';
484
- import { DismissibleItemDto } from '@dismissible/nestjs-item';
687
+ import { Injectable, Inject } from '@nestjs/common';
688
+ import { IDismissibleService, DISMISSIBLE_SERVICE } from '@dismissible/nestjs-core';
485
689
 
486
690
  @Injectable()
487
- export class RedisStorageAdapter implements IDismissibleStorage {
488
- async get(userId: string, itemId: string): Promise<DismissibleItemDto | null> {
489
- // Your implementation
490
- }
491
-
492
- async create(userId: string, item: DismissibleItemDto): Promise<void> {
493
- // Your implementation
494
- }
495
-
496
- async update(userId: string, item: DismissibleItemDto): Promise<void> {
497
- // Your implementation
691
+ export class MyService {
692
+ constructor(
693
+ @Inject(DISMISSIBLE_SERVICE)
694
+ private readonly dismissibleService: IDismissibleService,
695
+ ) {}
696
+
697
+ async myMethod(userId: string, itemId: string) {
698
+ // Your custom implementation will be injected if you've overridden it
699
+ return this.dismissibleService.getOrCreate(itemId, userId);
498
700
  }
499
701
  }
500
702
  ```