@dismissible/nestjs-core 2.0.2-alpha.99ffc23.0 → 2.0.2-canary.2913ba8.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 (65) hide show
  1. package/README.md +424 -232
  2. package/package.json +7 -7
  3. package/src/api/dismissible-item.mapper.d.ts +2 -1
  4. package/src/api/dismissible-item.mapper.interface.d.ts +17 -0
  5. package/src/api/dismissible-item.mapper.interface.js +8 -0
  6. package/src/api/dismissible-item.mapper.interface.js.map +1 -0
  7. package/src/api/dismissible-item.mapper.js.map +1 -1
  8. package/src/api/index.d.ts +1 -0
  9. package/src/api/index.js +1 -0
  10. package/src/api/index.js.map +1 -1
  11. package/src/api/use-cases/dismiss/dismiss.controller.d.ts +4 -4
  12. package/src/api/use-cases/dismiss/dismiss.controller.js +7 -6
  13. package/src/api/use-cases/dismiss/dismiss.controller.js.map +1 -1
  14. package/src/api/use-cases/get-or-create/get-or-create.controller.d.ts +4 -4
  15. package/src/api/use-cases/get-or-create/get-or-create.controller.js +7 -6
  16. package/src/api/use-cases/get-or-create/get-or-create.controller.js.map +1 -1
  17. package/src/api/use-cases/restore/restore.controller.d.ts +4 -4
  18. package/src/api/use-cases/restore/restore.controller.js +7 -6
  19. package/src/api/use-cases/restore/restore.controller.js.map +1 -1
  20. package/src/core/dismissible-core.service.d.ts +6 -5
  21. package/src/core/dismissible-core.service.interface.d.ts +48 -0
  22. package/src/core/dismissible-core.service.interface.js +8 -0
  23. package/src/core/dismissible-core.service.interface.js.map +1 -0
  24. package/src/core/dismissible-core.service.js +6 -5
  25. package/src/core/dismissible-core.service.js.map +1 -1
  26. package/src/core/dismissible.service.d.ts +5 -4
  27. package/src/core/dismissible.service.interface.d.ts +33 -0
  28. package/src/core/dismissible.service.interface.js +8 -0
  29. package/src/core/dismissible.service.interface.js.map +1 -0
  30. package/src/core/dismissible.service.js +5 -4
  31. package/src/core/dismissible.service.js.map +1 -1
  32. package/src/core/hook-runner.interface.d.ts +56 -0
  33. package/src/core/hook-runner.interface.js +8 -0
  34. package/src/core/hook-runner.interface.js.map +1 -0
  35. package/src/core/hook-runner.service.d.ts +2 -1
  36. package/src/core/hook-runner.service.js.map +1 -1
  37. package/src/core/index.d.ts +3 -0
  38. package/src/core/index.js +3 -0
  39. package/src/core/index.js.map +1 -1
  40. package/src/dismissible.module.js +31 -1
  41. package/src/dismissible.module.js.map +1 -1
  42. package/src/response/index.d.ts +1 -0
  43. package/src/response/index.js +1 -0
  44. package/src/response/index.js.map +1 -1
  45. package/src/response/response.service.d.ts +2 -1
  46. package/src/response/response.service.interface.d.ts +19 -0
  47. package/src/response/response.service.interface.js +8 -0
  48. package/src/response/response.service.interface.js.map +1 -0
  49. package/src/response/response.service.js.map +1 -1
  50. package/src/utils/date/date.service.d.ts +2 -1
  51. package/src/utils/date/date.service.interface.d.ts +21 -0
  52. package/src/utils/date/date.service.interface.js +8 -0
  53. package/src/utils/date/date.service.interface.js.map +1 -0
  54. package/src/utils/date/date.service.js.map +1 -1
  55. package/src/utils/date/index.d.ts +1 -0
  56. package/src/utils/date/index.js +1 -0
  57. package/src/utils/date/index.js.map +1 -1
  58. package/src/utils/dismissible.helper.d.ts +2 -1
  59. package/src/utils/dismissible.helper.interface.d.ts +14 -0
  60. package/src/utils/dismissible.helper.interface.js +8 -0
  61. package/src/utils/dismissible.helper.interface.js.map +1 -0
  62. package/src/utils/dismissible.helper.js.map +1 -1
  63. package/src/utils/index.d.ts +1 -0
  64. package/src/utils/index.js +1 -0
  65. package/src/utils/index.js.map +1 -1
package/README.md CHANGED
@@ -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,160 @@ 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
224
297
 
225
- ### Custom Lifecycle Hooks
298
+ ---
226
299
 
227
- Lifecycle hooks allow you to intercept operations and add custom logic, validation, or mutations:
300
+ ### `imports`
301
+
302
+ Adds additional modules to the DismissibleModule's imports. Useful for injecting dependencies that your hooks or providers need.
303
+
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 } from '@nestjs/common';
371
+ import { DismissibleModule, DismissibleService } from '@dismissible/nestjs-core';
372
+
373
+ @Controller('custom')
374
+ class CustomController {
375
+ constructor(private dismissibleService: DismissibleService) {}
376
+
377
+ @Get(':userId/:itemId')
378
+ async getItem(@Param('userId') userId: string, @Param('itemId') itemId: string) {
379
+ return this.dismissibleService.getOrCreate(itemId, userId);
380
+ }
381
+ }
286
382
 
287
383
  @Module({
288
384
  imports: [
289
385
  DismissibleModule.forRoot({
290
- hooks: [AuditHook],
386
+ controllers: [CustomController],
291
387
  }),
292
388
  ],
293
389
  })
294
390
  export class AppModule {}
295
391
  ```
296
392
 
297
- ### Listening to Events
393
+ To disable the REST API entirely, pass an empty array:
298
394
 
299
- The library emits events for all operations. Listen to them using NestJS's `EventEmitter2`:
395
+ ```typescript
396
+ @Module({
397
+ imports: [
398
+ DismissibleModule.forRoot({
399
+ controllers: [],
400
+ }),
401
+ ],
402
+ })
403
+ export class AppModule {}
404
+ ```
405
+
406
+ ---
407
+
408
+ ## Using the Service Directly
409
+
410
+ Instead of using the built-in REST API, you can inject `DismissibleService` into your own controllers or services:
411
+
412
+ ```typescript
413
+ import { Controller, Get, Param, Delete, Post } from '@nestjs/common';
414
+ import { DismissibleService } from '@dismissible/nestjs-core';
415
+
416
+ @Controller('features')
417
+ export class FeaturesController {
418
+ constructor(private readonly dismissibleService: DismissibleService) {}
419
+
420
+ @Get(':userId/items/:itemId')
421
+ async getOrCreateItem(@Param('userId') userId: string, @Param('itemId') itemId: string) {
422
+ const result = await this.dismissibleService.getOrCreate(itemId, userId);
423
+ return {
424
+ item: result.item,
425
+ wasCreated: result.created,
426
+ };
427
+ }
428
+
429
+ @Delete(':userId/items/:itemId')
430
+ async dismissItem(@Param('userId') userId: string, @Param('itemId') itemId: string) {
431
+ const result = await this.dismissibleService.dismiss(itemId, userId);
432
+ return { item: result.item };
433
+ }
434
+
435
+ @Post(':userId/items/:itemId/restore')
436
+ async restoreItem(@Param('userId') userId: string, @Param('itemId') itemId: string) {
437
+ const result = await this.dismissibleService.restore(itemId, userId);
438
+ return { item: result.item };
439
+ }
440
+ }
441
+ ```
442
+
443
+ ## Events
444
+
445
+ The library emits events for all operations using NestJS's EventEmitter2:
300
446
 
301
447
  ```typescript
302
448
  import { Injectable } from '@nestjs/common';
@@ -305,7 +451,6 @@ import {
305
451
  ItemCreatedEvent,
306
452
  ItemDismissedEvent,
307
453
  ItemRestoredEvent,
308
- ItemRetrievedEvent,
309
454
  DismissibleEvents,
310
455
  } from '@dismissible/nestjs-core';
311
456
 
@@ -313,188 +458,235 @@ import {
313
458
  export class AnalyticsService {
314
459
  @OnEvent(DismissibleEvents.ITEM_CREATED)
315
460
  handleItemCreated(event: ItemCreatedEvent) {
316
- // Track item creation in analytics
317
- console.log(`Analytics: Item ${event.id} created for user ${event.userId}`);
461
+ console.log(`Item ${event.id} created for user ${event.userId}`);
318
462
  }
319
463
 
320
464
  @OnEvent(DismissibleEvents.ITEM_DISMISSED)
321
465
  handleItemDismissed(event: ItemDismissedEvent) {
322
- // Track dismissals
323
- console.log(`Analytics: Item ${event.id} dismissed by user ${event.userId}`);
466
+ console.log(`Item ${event.id} dismissed by user ${event.userId}`);
324
467
  }
325
468
 
326
469
  @OnEvent(DismissibleEvents.ITEM_RESTORED)
327
470
  handleItemRestored(event: ItemRestoredEvent) {
328
- // Track restorations
329
- console.log(`Analytics: Item ${event.id} restored by user ${event.userId}`);
471
+ console.log(`Item ${event.id} restored by user ${event.userId}`);
330
472
  }
331
473
  }
332
474
  ```
333
475
 
334
- ### Custom Logger
476
+ Available events:
477
+
478
+ - `DismissibleEvents.ITEM_CREATED` - New item created
479
+ - `DismissibleEvents.ITEM_RETRIEVED` - Existing item retrieved
480
+ - `DismissibleEvents.ITEM_DISMISSED` - Item dismissed
481
+ - `DismissibleEvents.ITEM_RESTORED` - Item restored
335
482
 
336
- Provide a custom logger implementation:
483
+ ## Overriding Services
484
+
485
+ All core services can be overridden using symbol-based dependency injection tokens. This allows you to provide custom implementations while maintaining type safety.
486
+
487
+ ### Available Service Tokens
488
+
489
+ | Token | Interface | Description |
490
+ | ------------------------------ | ------------------------- | ----------------------------------------------- |
491
+ | `DISMISSIBLE_SERVICE` | `IDismissibleService` | Main orchestration service |
492
+ | `DISMISSIBLE_CORE_SERVICE` | `IDismissibleCoreService` | Core business logic service |
493
+ | `DISMISSIBLE_HOOK_RUNNER` | `IHookRunner` | Lifecycle hook execution |
494
+ | `DISMISSIBLE_HELPER` | `IDismissibleHelper` | Helper utilities |
495
+ | `DISMISSIBLE_DATE_SERVICE` | `IDateService` | Date operations |
496
+ | `DISMISSIBLE_RESPONSE_SERVICE` | `IResponseService` | HTTP response formatting |
497
+ | `DISMISSIBLE_ITEM_MAPPER` | `IDismissibleItemMapper` | Domain to DTO mapping |
498
+ | `DISMISSIBLE_ITEM_FACTORY` | `IDismissibleItemFactory` | Item creation (from `@dismissible/nestjs-item`) |
499
+
500
+ ### Example: Overriding the Date Service
501
+
502
+ Override the date service to control time in tests or add custom behavior:
337
503
 
338
504
  ```typescript
339
- import { Injectable } from '@nestjs/common';
340
- import { IDismissibleLogger } from '@dismissible/nestjs-logger';
341
- import { DismissibleModule } from '@dismissible/nestjs-core';
505
+ import { Injectable, Module } from '@nestjs/common';
506
+ import {
507
+ DismissibleModule,
508
+ IDateService,
509
+ DISMISSIBLE_DATE_SERVICE,
510
+ } from '@dismissible/nestjs-core';
342
511
 
343
512
  @Injectable()
344
- export class CustomLogger implements IDismissibleLogger {
345
- debug(message: string, context?: any) {
346
- // Your custom logging logic
347
- console.log(`[DEBUG] ${message}`, context);
513
+ class CustomDateService implements IDateService {
514
+ getNow(): Date {
515
+ // Custom implementation - e.g., use a fixed time for testing
516
+ return new Date('2024-01-01T00:00:00.000Z');
348
517
  }
349
518
 
350
- info(message: string, context?: any) {
351
- console.log(`[INFO] ${message}`, context);
352
- }
353
-
354
- warn(message: string, context?: any) {
355
- console.warn(`[WARN] ${message}`, context);
519
+ parseIso(isoString: string): Date {
520
+ return new Date(isoString);
356
521
  }
357
522
 
358
- error(message: string, context?: any) {
359
- console.error(`[ERROR] ${message}`, context);
523
+ toIso(date: Date): string {
524
+ return date.toISOString();
360
525
  }
361
526
  }
362
527
 
363
528
  @Module({
364
529
  imports: [
365
530
  DismissibleModule.forRoot({
366
- logger: CustomLogger,
531
+ providers: [
532
+ CustomDateService,
533
+ { provide: DISMISSIBLE_DATE_SERVICE, useExisting: CustomDateService },
534
+ ],
367
535
  }),
368
536
  ],
369
537
  })
370
538
  export class AppModule {}
371
539
  ```
372
540
 
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
541
+ ### Example: Overriding the Main Service
388
542
 
389
- Returns: `Promise<IGetOrCreateServiceResponse>`
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>`
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
543
+ Override the main dismissible service for custom orchestration logic:
412
544
 
413
545
  ```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
546
+ import { Injectable, Inject, Module } from '@nestjs/common';
547
+ import {
548
+ DismissibleModule,
549
+ IDismissibleService,
550
+ IDismissibleCoreService,
551
+ DISMISSIBLE_SERVICE,
552
+ DISMISSIBLE_CORE_SERVICE,
553
+ IGetOrCreateServiceResponse,
554
+ IDismissServiceResponse,
555
+ IRestoreServiceResponse,
556
+ } from '@dismissible/nestjs-core';
557
+ import { IRequestContext } from '@dismissible/nestjs-request';
441
558
 
442
- Dismiss and restore events also include:
559
+ @Injectable()
560
+ class CustomDismissibleService implements IDismissibleService {
561
+ constructor(
562
+ @Inject(DISMISSIBLE_CORE_SERVICE)
563
+ private readonly coreService: IDismissibleCoreService,
564
+ ) {}
443
565
 
444
- - `previousItem: DismissibleItemDto` - The item state before the operation
566
+ async getOrCreate(
567
+ itemId: string,
568
+ userId: string,
569
+ context?: IRequestContext,
570
+ ): Promise<IGetOrCreateServiceResponse> {
571
+ // Add custom logic before/after core operation
572
+ console.log('Custom getOrCreate called');
573
+ return this.coreService.getOrCreate(itemId, userId);
574
+ }
445
575
 
446
- ## Lifecycle Hooks
576
+ async dismiss(
577
+ itemId: string,
578
+ userId: string,
579
+ context?: IRequestContext,
580
+ ): Promise<IDismissServiceResponse> {
581
+ return this.coreService.dismiss(itemId, userId);
582
+ }
447
583
 
448
- Hooks can implement any of the following methods:
584
+ async restore(
585
+ itemId: string,
586
+ userId: string,
587
+ context?: IRequestContext,
588
+ ): Promise<IRestoreServiceResponse> {
589
+ return this.coreService.restore(itemId, userId);
590
+ }
591
+ }
449
592
 
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
593
+ @Module({
594
+ imports: [
595
+ DismissibleModule.forRoot({
596
+ providers: [
597
+ CustomDismissibleService,
598
+ { provide: DISMISSIBLE_SERVICE, useExisting: CustomDismissibleService },
599
+ ],
600
+ }),
601
+ ],
602
+ })
603
+ export class AppModule {}
604
+ ```
458
605
 
459
- Hooks can:
606
+ ### Example: Overriding the Item Factory
460
607
 
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)
608
+ Override the item factory to customize how items are created:
464
609
 
465
- Hooks are executed in priority order (lower numbers first).
610
+ ```typescript
611
+ import { Injectable, Module } from '@nestjs/common';
612
+ import { DismissibleModule } from '@dismissible/nestjs-core';
613
+ import {
614
+ DismissibleItemDto,
615
+ IDismissibleItemFactory,
616
+ ICreateDismissibleItemOptions,
617
+ DISMISSIBLE_ITEM_FACTORY,
618
+ } from '@dismissible/nestjs-item';
466
619
 
467
- ## Storage Adapters
620
+ @Injectable()
621
+ class CustomItemFactory implements IDismissibleItemFactory {
622
+ create(options: ICreateDismissibleItemOptions): DismissibleItemDto {
623
+ const item = new DismissibleItemDto();
624
+ item.id = options.id;
625
+ item.userId = options.userId;
626
+ item.createdAt = options.createdAt;
627
+ item.dismissedAt = options.dismissedAt;
628
+ return item;
629
+ }
468
630
 
469
- ### In-Memory Storage (Default)
631
+ clone(item: DismissibleItemDto): DismissibleItemDto {
632
+ return this.create({
633
+ id: item.id,
634
+ createdAt: item.createdAt,
635
+ userId: item.userId,
636
+ dismissedAt: item.dismissedAt,
637
+ });
638
+ }
470
639
 
471
- The default storage adapter stores items in memory. Data is lost on application restart.
640
+ createDismissed(item: DismissibleItemDto, dismissedAt: Date): DismissibleItemDto {
641
+ return this.create({
642
+ id: item.id,
643
+ createdAt: item.createdAt,
644
+ userId: item.userId,
645
+ dismissedAt,
646
+ });
647
+ }
472
648
 
473
- ### PostgreSQL Storage
649
+ createRestored(item: DismissibleItemDto): DismissibleItemDto {
650
+ return this.create({
651
+ id: item.id,
652
+ createdAt: item.createdAt,
653
+ userId: item.userId,
654
+ dismissedAt: undefined,
655
+ });
656
+ }
657
+ }
474
658
 
475
- Use `PostgresStorageModule` for persistent storage. See the [Using PostgreSQL Storage](#using-postgresql-storage) section above.
659
+ @Module({
660
+ imports: [
661
+ DismissibleModule.forRoot({
662
+ providers: [
663
+ CustomItemFactory,
664
+ { provide: DISMISSIBLE_ITEM_FACTORY, useExisting: CustomItemFactory },
665
+ ],
666
+ }),
667
+ ],
668
+ })
669
+ export class AppModule {}
670
+ ```
476
671
 
477
- ### Custom Storage Adapter
672
+ ### Injecting Overridable Services
478
673
 
479
- Implement the `IDismissibleStorage` interface to create a custom storage adapter:
674
+ When injecting these services in your own code, use the symbol tokens for maximum flexibility:
480
675
 
481
676
  ```typescript
482
- import { Injectable } from '@nestjs/common';
483
- import { IDismissibleStorage } from '@dismissible/nestjs-storage';
484
- import { DismissibleItemDto } from '@dismissible/nestjs-item';
677
+ import { Injectable, Inject } from '@nestjs/common';
678
+ import { IDismissibleService, DISMISSIBLE_SERVICE } from '@dismissible/nestjs-core';
485
679
 
486
680
  @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
681
+ export class MyService {
682
+ constructor(
683
+ @Inject(DISMISSIBLE_SERVICE)
684
+ private readonly dismissibleService: IDismissibleService,
685
+ ) {}
686
+
687
+ async myMethod(userId: string, itemId: string) {
688
+ // Your custom implementation will be injected if you've overridden it
689
+ return this.dismissibleService.getOrCreate(itemId, userId);
498
690
  }
499
691
  }
500
692
  ```