@flusys/nestjs-shared 7.0.0 → 7.0.2
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.
- package/README.md +132 -84
- package/cjs/classes/api-service.class.js +37 -47
- package/cjs/classes/api-service.class.spec.js +120 -12
- package/cjs/classes/event-bus.registry.js +2 -3
- package/cjs/classes/event-bus.registry.spec.js +49 -24
- package/cjs/classes/event-transports/kafka-transport.optional.spec.js +1 -2
- package/cjs/classes/event-transports/memory-transport.js +3 -4
- package/cjs/classes/event-transports/memory-transport.spec.js +1 -2
- package/cjs/classes/event-transports/rabbitmq-transport.optional.spec.js +1 -2
- package/cjs/classes/hybrid-event-bus.class.js +23 -3
- package/cjs/classes/hybrid-event-bus.class.spec.js +31 -2
- package/cjs/enums/index.js +0 -1
- package/cjs/modules/event-bus/event-bus.integration.spec.js +1 -2
- package/cjs/utils/event-pattern.util.js +6 -1
- package/cjs/utils/event-pattern.util.spec.js +20 -3
- package/classes/api-service.class.d.ts +8 -12
- package/classes/domain-event.publisher.d.ts +2 -2
- package/classes/event-bus.registry.d.ts +2 -2
- package/classes/event-transports/memory-transport.d.ts +1 -1
- package/classes/hybrid-event-bus.class.d.ts +2 -0
- package/enums/index.d.ts +0 -1
- package/fesm/classes/api-service.class.js +37 -47
- package/fesm/classes/api-service.class.spec.js +120 -12
- package/fesm/classes/event-bus.registry.js +2 -3
- package/fesm/classes/event-bus.registry.spec.js +49 -24
- package/fesm/classes/event-transports/kafka-transport.optional.spec.js +1 -2
- package/fesm/classes/event-transports/memory-transport.js +7 -4
- package/fesm/classes/event-transports/memory-transport.spec.js +1 -2
- package/fesm/classes/event-transports/rabbitmq-transport.optional.spec.js +1 -2
- package/fesm/classes/hybrid-event-bus.class.js +23 -3
- package/fesm/classes/hybrid-event-bus.class.spec.js +31 -2
- package/fesm/enums/index.js +0 -1
- package/fesm/modules/event-bus/event-bus.integration.spec.js +1 -2
- package/fesm/utils/event-pattern.util.js +6 -1
- package/fesm/utils/event-pattern.util.spec.js +20 -3
- package/interfaces/event-bus.interface.d.ts +3 -6
- package/package.json +2 -2
- package/cjs/enums/event-action.enum.js +0 -18
- package/enums/event-action.enum.d.ts +0 -7
- package/fesm/enums/event-action.enum.js +0 -8
package/README.md
CHANGED
|
@@ -59,19 +59,21 @@ export class Product extends Identity {
|
|
|
59
59
|
}
|
|
60
60
|
```
|
|
61
61
|
|
|
62
|
-
## 3. Generic CRUD — `createApiController` + `
|
|
62
|
+
## 3. Generic CRUD — `createApiController` + `ApiService`
|
|
63
63
|
|
|
64
64
|
### Service (REQUEST-scoped, DataSource Provider pattern)
|
|
65
65
|
|
|
66
|
+
The constructor takes the event entity name, the cache, `UtilsService`, the logger
|
|
67
|
+
name, whether reads are cacheable, the event module name, the DataSource provider
|
|
68
|
+
and the entity. All of them are required, so a service cannot silently skip one.
|
|
69
|
+
|
|
66
70
|
```typescript
|
|
67
|
-
import { Injectable, Scope
|
|
68
|
-
import {
|
|
69
|
-
import { RequestScopedApiService } from '@flusys/nestjs-shared/classes';
|
|
70
|
-
import { HybridCache } from '@flusys/nestjs-shared/classes';
|
|
71
|
+
import { Inject, Injectable, Scope } from '@nestjs/common';
|
|
72
|
+
import { ApiService, HybridCache } from '@flusys/nestjs-shared/classes';
|
|
71
73
|
import { UtilsService } from '@flusys/nestjs-shared/modules';
|
|
72
74
|
|
|
73
75
|
@Injectable({ scope: Scope.REQUEST }) // required
|
|
74
|
-
export class ProductService extends
|
|
76
|
+
export class ProductService extends ApiService<
|
|
75
77
|
CreateProductDto,
|
|
76
78
|
UpdateProductDto,
|
|
77
79
|
IProduct,
|
|
@@ -80,23 +82,49 @@ export class ProductService extends RequestScopedApiService<
|
|
|
80
82
|
constructor(
|
|
81
83
|
@Inject('CACHE_INSTANCE') protected override cacheManager: HybridCache,
|
|
82
84
|
@Inject(UtilsService) protected override utilsService: UtilsService,
|
|
83
|
-
@Inject(
|
|
84
|
-
@Inject(ProductDataSourceProvider) private readonly dsp: ProductDataSourceProvider,
|
|
85
|
+
@Inject(ProductDataSourceProvider) dataSourceProvider: ProductDataSourceProvider,
|
|
85
86
|
) {
|
|
86
|
-
super(
|
|
87
|
+
super(
|
|
88
|
+
PRODUCT_EVENT_ENTITIES.PRODUCT,
|
|
89
|
+
cacheManager,
|
|
90
|
+
utilsService,
|
|
91
|
+
ProductService.name,
|
|
92
|
+
true,
|
|
93
|
+
PRODUCT_EVENT_MODULE,
|
|
94
|
+
dataSourceProvider,
|
|
95
|
+
Product,
|
|
96
|
+
);
|
|
87
97
|
}
|
|
98
|
+
}
|
|
99
|
+
```
|
|
88
100
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
}
|
|
101
|
+
A service whose table depends on runtime configuration passes the choice in the
|
|
102
|
+
same argument. Read the injected config through the **constructor parameter**, not
|
|
103
|
+
`this` - `this` is not available until `super()` returns:
|
|
93
104
|
|
|
94
|
-
|
|
95
|
-
|
|
105
|
+
```typescript
|
|
106
|
+
constructor(
|
|
107
|
+
@Inject('CACHE_INSTANCE') protected override cacheManager: HybridCache,
|
|
108
|
+
@Inject(UtilsService) protected override utilsService: UtilsService,
|
|
109
|
+
@Inject(ProductConfigService) private readonly config: ProductConfigService,
|
|
110
|
+
@Inject(ProductDataSourceProvider) dataSourceProvider: ProductDataSourceProvider,
|
|
111
|
+
) {
|
|
112
|
+
super(
|
|
113
|
+
PRODUCT_EVENT_ENTITIES.PRODUCT,
|
|
114
|
+
cacheManager,
|
|
115
|
+
utilsService,
|
|
116
|
+
ProductService.name,
|
|
117
|
+
true,
|
|
118
|
+
PRODUCT_EVENT_MODULE,
|
|
119
|
+
dataSourceProvider,
|
|
120
|
+
config.isCompanyFeatureEnabled() ? ProductWithCompany : Product,
|
|
121
|
+
);
|
|
96
122
|
}
|
|
97
|
-
}
|
|
98
123
|
```
|
|
99
124
|
|
|
125
|
+
Services are `Scope.REQUEST`, so the configuration is read once per request either
|
|
126
|
+
way. The repository itself is still created lazily, on first use.
|
|
127
|
+
|
|
100
128
|
### Controller (factory function)
|
|
101
129
|
|
|
102
130
|
```typescript
|
|
@@ -297,7 +325,7 @@ events — no per-service code. Turn it on in the module's own config:
|
|
|
297
325
|
StorageModule.forRoot({
|
|
298
326
|
config: {
|
|
299
327
|
...dbConfig,
|
|
300
|
-
events: { enabled: true
|
|
328
|
+
events: { enabled: true },
|
|
301
329
|
},
|
|
302
330
|
});
|
|
303
331
|
```
|
|
@@ -307,22 +335,45 @@ StorageModule.forRoot({
|
|
|
307
335
|
| Option | Type | Default | Effect |
|
|
308
336
|
| ----------------- | --------------------------- | ------- | ---------------------------------------------------------------------- |
|
|
309
337
|
| `enabled` | `boolean` | `false` | Master switch for the module. Everything else is ignored when off. |
|
|
310
|
-
| `actions` | `
|
|
311
|
-
| `entities` | `string[]` | all | Allowlist of entity names. Omit to publish every entity. |
|
|
312
|
-
| `excludeEntities` | `string[]` | `[]` | Denylist, applied before `entities`. |
|
|
338
|
+
| `actions` | `string[]` | all | Allowlist of `entity.action` or bare `action` entries. Omit to publish all. |
|
|
313
339
|
| `includePayload` | `boolean` | `true` | Attach the saved rows as `payload.data`. Ids and metadata always ride. |
|
|
314
340
|
|
|
315
|
-
|
|
341
|
+
Two rules decide what actually reaches a consumer:
|
|
316
342
|
|
|
317
343
|
1. `actions` is **one allowlist covering CRUD and domain actions together**. Listing only
|
|
318
344
|
`['logged-in']` silences that module's `created`/`updated`/`deleted` too - name
|
|
319
345
|
everything you want kept, or omit `actions` entirely.
|
|
320
|
-
2.
|
|
321
|
-
|
|
322
|
-
`
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
346
|
+
2. An entry is either `entity.action`, which covers that one entity, or a bare action,
|
|
347
|
+
which covers every entity of the module. So `role.updated` keeps role updates and
|
|
348
|
+
drops `action.updated`, while `updated` keeps every entity's updates. Entries are
|
|
349
|
+
matched as routing patterns, so `role.*` and `*.purged` work as well.
|
|
350
|
+
|
|
351
|
+
There is no separate entity filter: an enabled module publishes under every entity it has
|
|
352
|
+
unless an entry narrows it, so a package gaining one is picked up with no config change.
|
|
353
|
+
|
|
354
|
+
Mixing the two forms narrows one entity without touching the others:
|
|
355
|
+
|
|
356
|
+
```typescript
|
|
357
|
+
import { IAM_EVENT_ACTIONS } from '@flusys/nestjs-iam/config';
|
|
358
|
+
|
|
359
|
+
IAMModule.forRoot({
|
|
360
|
+
config: {
|
|
361
|
+
...dbConfig,
|
|
362
|
+
events: {
|
|
363
|
+
enabled: true,
|
|
364
|
+
actions: [
|
|
365
|
+
'created',
|
|
366
|
+
'deleted',
|
|
367
|
+
'role.updated',
|
|
368
|
+
`user_role.${IAM_EVENT_ACTIONS.PERMISSIONS_ASSIGNED}`,
|
|
369
|
+
],
|
|
370
|
+
},
|
|
371
|
+
},
|
|
372
|
+
});
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
`iam.role.updated` publishes; `iam.action.updated` does not; both still publish `created`
|
|
376
|
+
and `deleted`.
|
|
326
377
|
|
|
327
378
|
Event names are always `<module>.<entity>.<action>`. CRUD lifecycle actions are
|
|
328
379
|
`created`, `updated`, `deleted`, `restored`, `purged`. Events are published after the
|
|
@@ -336,15 +387,21 @@ there is no row left to attach.
|
|
|
336
387
|
Payloads are walked before they leave the process: credential-like fields become
|
|
337
388
|
`[REDACTED]`, a relation that loops back becomes `[CIRCULAR]`, anything past 8 levels deep
|
|
338
389
|
becomes `[TRUNCATED]`, and a Buffer becomes `[BINARY]`. Saved TypeORM rows with
|
|
339
|
-
bidirectional relations are therefore safe to publish.
|
|
390
|
+
bidirectional relations are therefore safe to publish. The `config` blob on a
|
|
391
|
+
`storageConfig` or `emailConfig` row is redacted whole - S3, Azure, SFTP and SMTP
|
|
392
|
+
credentials live in it - so those rows publish their id, name and provider and nothing
|
|
393
|
+
else. Redaction is a package default, not something an application configures, so it
|
|
394
|
+
cannot be switched off by a config edit.
|
|
340
395
|
|
|
341
396
|
### Registering each module
|
|
342
397
|
|
|
343
398
|
Every feature module takes the same `events` block in its own config, and every one of
|
|
344
399
|
them also has a `forRootAsync` variant that returns the identical `config`. The blocks
|
|
345
400
|
below spell out all options with that module's real entity and action names, so each is
|
|
346
|
-
copy-pasteable.
|
|
347
|
-
|
|
401
|
+
copy-pasteable. Write the CRUD half as literals - `created`, `updated`, `deleted`,
|
|
402
|
+
`restored`, `purged` never change - and take every other action from its package's own
|
|
403
|
+
`*_EVENT_ACTIONS` constant, so a rename there fails the build instead of silently muting
|
|
404
|
+
the event. Each package exports its entity names the same way (`STORAGE_EVENT_ENTITIES`).
|
|
348
405
|
|
|
349
406
|
In an application, keep the per-module blocks in one map rather than scattering them
|
|
350
407
|
across registrations - `flusysnest/src/config/modules.config.ts` is the reference:
|
|
@@ -355,14 +412,17 @@ const domainEventsEnabled = envConfig.getBoolean('ENABLE_DOMAIN_EVENTS', false);
|
|
|
355
412
|
export const moduleEventsConfig: Record<FeatureModuleName, IModuleEventsConfig> = {
|
|
356
413
|
storage: {
|
|
357
414
|
enabled: domainEventsEnabled,
|
|
358
|
-
actions: [
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
415
|
+
actions: [
|
|
416
|
+
'file_manager.created',
|
|
417
|
+
'file_manager.updated',
|
|
418
|
+
'file_manager.deleted',
|
|
419
|
+
'file_manager.restored',
|
|
420
|
+
'file_manager.purged',
|
|
421
|
+
`file_manager.${STORAGE_EVENT_ACTIONS.UPLOADED}`,
|
|
422
|
+
`file_manager.${STORAGE_EVENT_ACTIONS.REMOVED}`,
|
|
423
|
+
'folder.created',
|
|
424
|
+
// ...one line per entity and action the module can publish
|
|
363
425
|
],
|
|
364
|
-
excludeEntities: [STORAGE_EVENT_ENTITIES.STORAGE_CONFIG],
|
|
365
|
-
includePayload: true,
|
|
366
426
|
},
|
|
367
427
|
// ...one entry per feature module
|
|
368
428
|
};
|
|
@@ -374,28 +434,26 @@ export function getBaseModuleConfig(moduleName: FeatureModuleName) {
|
|
|
374
434
|
AuthModule.forRoot({ config: { ...authConfig, ...getBaseModuleConfig('auth') } });
|
|
375
435
|
```
|
|
376
436
|
|
|
377
|
-
Spelling `
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
each
|
|
383
|
-
|
|
384
|
-
|
|
437
|
+
Spelling every `entity.action` pair out in full - CRUD lifecycle ones included, rather
|
|
438
|
+
than spreading a shared array or leaning on bare actions - is the same as omitting the
|
|
439
|
+
option, and makes the file a readable inventory of what each module publishes: delete one
|
|
440
|
+
line to stop publishing that one pair. The trade-off is that a package gaining an entity
|
|
441
|
+
or an action stays silent until it is listed, so keep a test asserting the list covers
|
|
442
|
+
each package's own `*_EVENT_ENTITIES` and `*_EVENT_ACTIONS` constants. One env flag still
|
|
443
|
+
arms every module, but each keeps its own block, so narrowing one module never touches
|
|
444
|
+
the others.
|
|
385
445
|
|
|
386
446
|
#### auth
|
|
387
447
|
|
|
388
448
|
```typescript
|
|
389
|
-
import { AUTH_EVENT_ACTIONS
|
|
449
|
+
import { AUTH_EVENT_ACTIONS } from '@flusys/nestjs-auth/config';
|
|
390
450
|
|
|
391
451
|
AuthModule.forRoot({
|
|
392
452
|
config: {
|
|
393
453
|
...dbConfig,
|
|
394
454
|
events: {
|
|
395
455
|
enabled: true,
|
|
396
|
-
actions: [
|
|
397
|
-
entities: [AUTH_EVENT_ENTITIES.USER, AUTH_EVENT_ENTITIES.SESSION],
|
|
398
|
-
excludeEntities: ['company_branch'],
|
|
456
|
+
actions: ['appuser.created', `session.${AUTH_EVENT_ACTIONS.LOGGED_IN}`],
|
|
399
457
|
includePayload: false,
|
|
400
458
|
},
|
|
401
459
|
},
|
|
@@ -413,17 +471,18 @@ AuthModule.forRoot({
|
|
|
413
471
|
#### iam
|
|
414
472
|
|
|
415
473
|
```typescript
|
|
416
|
-
import { IAM_EVENT_ACTIONS
|
|
474
|
+
import { IAM_EVENT_ACTIONS } from '@flusys/nestjs-iam/config';
|
|
417
475
|
|
|
418
476
|
IAMModule.forRoot({
|
|
419
477
|
config: {
|
|
420
478
|
...dbConfig,
|
|
421
479
|
events: {
|
|
422
480
|
enabled: true,
|
|
423
|
-
actions: [
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
481
|
+
actions: [
|
|
482
|
+
'role.created',
|
|
483
|
+
'role.deleted',
|
|
484
|
+
`user_role.${IAM_EVENT_ACTIONS.PERMISSIONS_ASSIGNED}`,
|
|
485
|
+
],
|
|
427
486
|
},
|
|
428
487
|
},
|
|
429
488
|
});
|
|
@@ -437,17 +496,14 @@ IAMModule.forRoot({
|
|
|
437
496
|
#### storage
|
|
438
497
|
|
|
439
498
|
```typescript
|
|
440
|
-
import { STORAGE_EVENT_ACTIONS
|
|
499
|
+
import { STORAGE_EVENT_ACTIONS } from '@flusys/nestjs-storage/config';
|
|
441
500
|
|
|
442
501
|
StorageModule.forRoot({
|
|
443
502
|
config: {
|
|
444
503
|
...dbConfig,
|
|
445
504
|
events: {
|
|
446
505
|
enabled: true,
|
|
447
|
-
actions: [
|
|
448
|
-
entities: [STORAGE_EVENT_ENTITIES.FILE, 'folder'],
|
|
449
|
-
excludeEntities: ['storageConfig'],
|
|
450
|
-
includePayload: true,
|
|
506
|
+
actions: ['folder.created', 'folder.deleted', `file_manager.${STORAGE_EVENT_ACTIONS.UPLOADED}`],
|
|
451
507
|
},
|
|
452
508
|
},
|
|
453
509
|
});
|
|
@@ -472,9 +528,7 @@ FormBuilderModule.forRoot({
|
|
|
472
528
|
...dbConfig,
|
|
473
529
|
events: {
|
|
474
530
|
enabled: true,
|
|
475
|
-
actions: [FORM_BUILDER_EVENT_ACTIONS.SUBMITTED],
|
|
476
|
-
entities: ['form_result'],
|
|
477
|
-
excludeEntities: [],
|
|
531
|
+
actions: [`form_result.${FORM_BUILDER_EVENT_ACTIONS.SUBMITTED}`],
|
|
478
532
|
includePayload: false,
|
|
479
533
|
},
|
|
480
534
|
},
|
|
@@ -492,16 +546,17 @@ audit consumer can work with `includePayload: false` and never see answer data.
|
|
|
492
546
|
#### email
|
|
493
547
|
|
|
494
548
|
```typescript
|
|
495
|
-
import { EMAIL_EVENT_ACTIONS
|
|
549
|
+
import { EMAIL_EVENT_ACTIONS } from '@flusys/nestjs-email/config';
|
|
496
550
|
|
|
497
551
|
EmailModule.forRoot({
|
|
498
552
|
config: {
|
|
499
553
|
...dbConfig,
|
|
500
554
|
events: {
|
|
501
555
|
enabled: true,
|
|
502
|
-
actions: [
|
|
503
|
-
|
|
504
|
-
|
|
556
|
+
actions: [
|
|
557
|
+
`email_message.${EMAIL_EVENT_ACTIONS.SENT}`,
|
|
558
|
+
`email_message.${EMAIL_EVENT_ACTIONS.FAILED}`,
|
|
559
|
+
],
|
|
505
560
|
includePayload: false,
|
|
506
561
|
},
|
|
507
562
|
},
|
|
@@ -524,10 +579,10 @@ EventManagerModule.forRoot({
|
|
|
524
579
|
...dbConfig,
|
|
525
580
|
events: {
|
|
526
581
|
enabled: true,
|
|
527
|
-
actions: [
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
582
|
+
actions: [
|
|
583
|
+
'event.created',
|
|
584
|
+
`event.${EVENT_MANAGER_EVENT_ACTIONS.PARTICIPANT_STATUS_CHANGED}`,
|
|
585
|
+
],
|
|
531
586
|
},
|
|
532
587
|
},
|
|
533
588
|
});
|
|
@@ -543,10 +598,7 @@ The participant event is keyed by the **event** id, with `participantId`,
|
|
|
543
598
|
#### notification
|
|
544
599
|
|
|
545
600
|
```typescript
|
|
546
|
-
import {
|
|
547
|
-
NOTIFICATION_EVENT_ACTIONS,
|
|
548
|
-
NOTIFICATION_EVENT_ENTITIES,
|
|
549
|
-
} from '@flusys/nestjs-notification/config';
|
|
601
|
+
import { NOTIFICATION_EVENT_ACTIONS } from '@flusys/nestjs-notification/config';
|
|
550
602
|
|
|
551
603
|
NotificationModule.forRoot({
|
|
552
604
|
config: {
|
|
@@ -554,9 +606,10 @@ NotificationModule.forRoot({
|
|
|
554
606
|
enableRealtime: true,
|
|
555
607
|
events: {
|
|
556
608
|
enabled: true,
|
|
557
|
-
actions: [
|
|
558
|
-
|
|
559
|
-
|
|
609
|
+
actions: [
|
|
610
|
+
`notification.${NOTIFICATION_EVENT_ACTIONS.SENT}`,
|
|
611
|
+
`notification.${NOTIFICATION_EVENT_ACTIONS.READ}`,
|
|
612
|
+
],
|
|
560
613
|
includePayload: false,
|
|
561
614
|
},
|
|
562
615
|
},
|
|
@@ -581,9 +634,7 @@ LocalizationModule.forRoot({
|
|
|
581
634
|
defaultLanguageCode: 'en',
|
|
582
635
|
events: {
|
|
583
636
|
enabled: true,
|
|
584
|
-
actions: [LOCALIZATION_EVENT_ACTIONS.BULK_UPDATED],
|
|
585
|
-
entities: ['translation'],
|
|
586
|
-
excludeEntities: ['translationKey'],
|
|
637
|
+
actions: [`translation.${LOCALIZATION_EVENT_ACTIONS.BULK_UPDATED}`],
|
|
587
638
|
includePayload: false,
|
|
588
639
|
},
|
|
589
640
|
},
|
|
@@ -609,10 +660,7 @@ TaskManagerModule.forRoot({
|
|
|
609
660
|
...dbConfig,
|
|
610
661
|
events: {
|
|
611
662
|
enabled: true,
|
|
612
|
-
actions: [TASK_MANAGER_EVENT_ACTIONS.MOVED
|
|
613
|
-
entities: ['task', 'task_list'],
|
|
614
|
-
excludeEntities: ['task_comment'],
|
|
615
|
-
includePayload: true,
|
|
663
|
+
actions: [`task.${TASK_MANAGER_EVENT_ACTIONS.MOVED}`, `task.${TASK_MANAGER_EVENT_ACTIONS.ASSIGNED}`],
|
|
616
664
|
},
|
|
617
665
|
},
|
|
618
666
|
});
|
|
@@ -13,7 +13,6 @@ const _typeorm = require("typeorm");
|
|
|
13
13
|
const _constants = require("../constants");
|
|
14
14
|
const _logactiondecorator = require("../decorators/log-action.decorator");
|
|
15
15
|
const _dtos = require("../dtos");
|
|
16
|
-
const _eventactionenum = require("../enums/event-action.enum");
|
|
17
16
|
const _interfaces = require("../interfaces");
|
|
18
17
|
const _utilsservice = require("../modules/utils/utils.service");
|
|
19
18
|
const _domaineventpublisher = require("./domain-event.publisher");
|
|
@@ -40,30 +39,19 @@ function _ts_decorate(decorators, target, key, desc) {
|
|
|
40
39
|
function _ts_metadata(k, v) {
|
|
41
40
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
42
41
|
}
|
|
43
|
-
const DEFAULT_EVENT_MODULE = 'app';
|
|
44
|
-
const OPERATION_EVENT_ACTIONS = {
|
|
45
|
-
insert: _eventactionenum.EventAction.CREATED,
|
|
46
|
-
insertMany: _eventactionenum.EventAction.CREATED,
|
|
47
|
-
update: _eventactionenum.EventAction.UPDATED,
|
|
48
|
-
updateMany: _eventactionenum.EventAction.UPDATED,
|
|
49
|
-
bulkUpsert: _eventactionenum.EventAction.UPDATED
|
|
50
|
-
};
|
|
51
42
|
const DELETE_EVENT_ACTIONS = {
|
|
52
|
-
delete:
|
|
53
|
-
restore:
|
|
43
|
+
delete: 'deleted',
|
|
44
|
+
restore: 'restored',
|
|
45
|
+
permanent: 'purged'
|
|
54
46
|
};
|
|
55
47
|
let ApiService = class ApiService {
|
|
56
|
-
resolveEntity() {
|
|
57
|
-
return this._entity;
|
|
58
|
-
}
|
|
59
48
|
getDataSourceProvider() {
|
|
60
49
|
return this._dataSourceProvider;
|
|
61
50
|
}
|
|
62
51
|
async ensureDataSourceRepository() {
|
|
63
52
|
if (!this.repositoryInitialized) {
|
|
64
|
-
const entity = this.resolveEntity();
|
|
65
53
|
const provider = this.getDataSourceProvider();
|
|
66
|
-
this.repository = await provider.getRepository(
|
|
54
|
+
this.repository = await provider.getRepository(this._entity);
|
|
67
55
|
this.repositoryInitialized = true;
|
|
68
56
|
}
|
|
69
57
|
}
|
|
@@ -75,11 +63,8 @@ let ApiService = class ApiService {
|
|
|
75
63
|
}
|
|
76
64
|
return repositories;
|
|
77
65
|
}
|
|
78
|
-
async getDataSourceForService() {
|
|
79
|
-
return this.getDataSourceProvider().getDataSource();
|
|
80
|
-
}
|
|
81
66
|
async insert(dto, user) {
|
|
82
|
-
return this.executeInTransaction('
|
|
67
|
+
return this.executeInTransaction('created', async (qr)=>{
|
|
83
68
|
await this.beforeInsertOperation(dto, user, qr);
|
|
84
69
|
const entities = await this.convertRequestDtoToEntity(dto, user);
|
|
85
70
|
const saved = await qr.manager.save(this.repository.target, entities);
|
|
@@ -91,7 +76,7 @@ let ApiService = class ApiService {
|
|
|
91
76
|
}, user);
|
|
92
77
|
}
|
|
93
78
|
async insertMany(dtos, user) {
|
|
94
|
-
return this.executeInTransaction('
|
|
79
|
+
return this.executeInTransaction('created', async (qr)=>{
|
|
95
80
|
await this.beforeInsertOperation(dtos, user, qr);
|
|
96
81
|
const entities = await this.convertRequestDtoToEntity(dtos, user);
|
|
97
82
|
const saved = await qr.manager.save(this.repository.target, entities);
|
|
@@ -103,7 +88,7 @@ let ApiService = class ApiService {
|
|
|
103
88
|
}, user);
|
|
104
89
|
}
|
|
105
90
|
async update(dto, user) {
|
|
106
|
-
return this.executeInTransaction('
|
|
91
|
+
return this.executeInTransaction('updated', async (qr)=>{
|
|
107
92
|
await this.beforeUpdateOperation(dto, user, qr);
|
|
108
93
|
const entities = await this.convertRequestDtoToEntity(dto, user);
|
|
109
94
|
const saved = await qr.manager.save(this.repository.target, entities);
|
|
@@ -115,7 +100,7 @@ let ApiService = class ApiService {
|
|
|
115
100
|
}, user);
|
|
116
101
|
}
|
|
117
102
|
async updateMany(dtos, user) {
|
|
118
|
-
return this.executeInTransaction('
|
|
103
|
+
return this.executeInTransaction('updated', async (qr)=>{
|
|
119
104
|
await this.beforeUpdateOperation(dtos, user, qr);
|
|
120
105
|
const entities = await this.convertRequestDtoToEntity(dtos, user);
|
|
121
106
|
const saved = await qr.manager.save(this.repository.target, entities);
|
|
@@ -127,7 +112,7 @@ let ApiService = class ApiService {
|
|
|
127
112
|
}, user);
|
|
128
113
|
}
|
|
129
114
|
async bulkUpsert(toInsert, toUpdate, user) {
|
|
130
|
-
return this.executeInTransaction(
|
|
115
|
+
return this.executeInTransaction(undefined, async (qr)=>{
|
|
131
116
|
const allResults = [];
|
|
132
117
|
const eventGroups = [];
|
|
133
118
|
if (toInsert.length > 0) {
|
|
@@ -137,7 +122,7 @@ let ApiService = class ApiService {
|
|
|
137
122
|
await this.afterInsertOperation(this.ensureArray(insertedSaved), toInsert, user, qr);
|
|
138
123
|
allResults.push(...this.ensureArray(insertedSaved));
|
|
139
124
|
eventGroups.push({
|
|
140
|
-
action:
|
|
125
|
+
action: 'created',
|
|
141
126
|
entities: this.ensureArray(insertedSaved)
|
|
142
127
|
});
|
|
143
128
|
}
|
|
@@ -148,7 +133,7 @@ let ApiService = class ApiService {
|
|
|
148
133
|
await this.afterUpdateOperation(this.ensureArray(updatedSaved), toUpdate, user, qr);
|
|
149
134
|
allResults.push(...this.ensureArray(updatedSaved));
|
|
150
135
|
eventGroups.push({
|
|
151
|
-
action:
|
|
136
|
+
action: 'updated',
|
|
152
137
|
entities: this.ensureArray(updatedSaved)
|
|
153
138
|
});
|
|
154
139
|
}
|
|
@@ -184,7 +169,7 @@ let ApiService = class ApiService {
|
|
|
184
169
|
if (error instanceof _common.NotFoundException) {
|
|
185
170
|
throw error;
|
|
186
171
|
}
|
|
187
|
-
this.handleError(error
|
|
172
|
+
this.handleError(error);
|
|
188
173
|
}
|
|
189
174
|
}
|
|
190
175
|
async findById(id, user, select) {
|
|
@@ -224,7 +209,7 @@ let ApiService = class ApiService {
|
|
|
224
209
|
if (error instanceof _common.NotFoundException) {
|
|
225
210
|
throw error;
|
|
226
211
|
}
|
|
227
|
-
this.handleError(error
|
|
212
|
+
this.handleError(error);
|
|
228
213
|
}
|
|
229
214
|
}
|
|
230
215
|
async getAll(search, filterAndPaginationDto, user) {
|
|
@@ -323,7 +308,7 @@ let ApiService = class ApiService {
|
|
|
323
308
|
}
|
|
324
309
|
return finalResponse;
|
|
325
310
|
} catch (error) {
|
|
326
|
-
this.handleError(error
|
|
311
|
+
this.handleError(error);
|
|
327
312
|
}
|
|
328
313
|
}
|
|
329
314
|
async delete(option, user) {
|
|
@@ -361,7 +346,7 @@ let ApiService = class ApiService {
|
|
|
361
346
|
]);
|
|
362
347
|
await this.publishLifecycleEvents([
|
|
363
348
|
{
|
|
364
|
-
action: DELETE_EVENT_ACTIONS[option.type]
|
|
349
|
+
action: DELETE_EVENT_ACTIONS[option.type],
|
|
365
350
|
entities: ids,
|
|
366
351
|
idsOnly: true
|
|
367
352
|
}
|
|
@@ -369,7 +354,7 @@ let ApiService = class ApiService {
|
|
|
369
354
|
return null;
|
|
370
355
|
} catch (error) {
|
|
371
356
|
await queryRunner.rollbackTransaction();
|
|
372
|
-
this.handleError(error
|
|
357
|
+
this.handleError(error);
|
|
373
358
|
} finally{
|
|
374
359
|
await queryRunner.release();
|
|
375
360
|
}
|
|
@@ -378,7 +363,7 @@ let ApiService = class ApiService {
|
|
|
378
363
|
for (const group of groups){
|
|
379
364
|
if (!group.entities.length) continue;
|
|
380
365
|
await (0, _domaineventpublisher.publishDomainEvent)({
|
|
381
|
-
module: this.moduleName
|
|
366
|
+
module: this.moduleName,
|
|
382
367
|
entity: this.entityName,
|
|
383
368
|
action: group.action,
|
|
384
369
|
ids: group.entities.map((entity)=>entity.id),
|
|
@@ -395,7 +380,7 @@ let ApiService = class ApiService {
|
|
|
395
380
|
*/ async publishDomainAction(action, input = {}) {
|
|
396
381
|
await (0, _domaineventpublisher.publishDomainEvent)({
|
|
397
382
|
...input,
|
|
398
|
-
module: this.moduleName
|
|
383
|
+
module: this.moduleName,
|
|
399
384
|
entity: this.entityName,
|
|
400
385
|
action
|
|
401
386
|
});
|
|
@@ -406,7 +391,7 @@ let ApiService = class ApiService {
|
|
|
406
391
|
async clearCacheForId(entities) {
|
|
407
392
|
await Promise.all(entities.map((item)=>this.utilsService.clearCache(this.entityName, this.cacheManager, item.id)));
|
|
408
393
|
}
|
|
409
|
-
handleError(error
|
|
394
|
+
handleError(error) {
|
|
410
395
|
if (error instanceof Error) {
|
|
411
396
|
throw error;
|
|
412
397
|
}
|
|
@@ -420,7 +405,11 @@ let ApiService = class ApiService {
|
|
|
420
405
|
value
|
|
421
406
|
];
|
|
422
407
|
}
|
|
423
|
-
|
|
408
|
+
/**
|
|
409
|
+
* `action` is the lifecycle action to publish for the whole batch; an
|
|
410
|
+
* operation that saves more than one kind of row - a bulk upsert - passes
|
|
411
|
+
* `undefined` and returns its own `eventGroups` instead.
|
|
412
|
+
*/ async executeInTransaction(action, fn, user) {
|
|
424
413
|
await this.ensureDataSourceRepository();
|
|
425
414
|
const qr = this.repository.manager.connection.createQueryRunner();
|
|
426
415
|
await qr.connect();
|
|
@@ -435,19 +424,19 @@ let ApiService = class ApiService {
|
|
|
435
424
|
this.clearCacheForId(savedArray)
|
|
436
425
|
]);
|
|
437
426
|
}
|
|
438
|
-
await this.publishLifecycleEvents(eventGroups ?? [
|
|
427
|
+
await this.publishLifecycleEvents(eventGroups ?? (action ? [
|
|
439
428
|
{
|
|
440
|
-
action
|
|
429
|
+
action,
|
|
441
430
|
entities: savedArray
|
|
442
431
|
}
|
|
443
|
-
], user ?? null);
|
|
432
|
+
] : []), user ?? null);
|
|
444
433
|
if (returnFirst) {
|
|
445
434
|
return this.convertEntityToResponseDto(savedArray[0], false);
|
|
446
435
|
}
|
|
447
436
|
return savedArray.map((e)=>this.convertEntityToResponseDto(e, false));
|
|
448
437
|
} catch (error) {
|
|
449
438
|
await qr.rollbackTransaction();
|
|
450
|
-
this.handleError(error
|
|
439
|
+
this.handleError(error);
|
|
451
440
|
} finally{
|
|
452
441
|
await qr.release();
|
|
453
442
|
}
|
|
@@ -520,7 +509,7 @@ let ApiService = class ApiService {
|
|
|
520
509
|
return Object.assign(existing, d);
|
|
521
510
|
}
|
|
522
511
|
delete d.id;
|
|
523
|
-
return Object.assign(
|
|
512
|
+
return Object.assign(this.repository.create(), d);
|
|
524
513
|
}
|
|
525
514
|
async convertArrayDtoToEntities(dtos, user) {
|
|
526
515
|
return await Promise.all(dtos.map((dto)=>this.convertSingleDtoToEntity(dto, user)));
|
|
@@ -531,10 +520,11 @@ let ApiService = class ApiService {
|
|
|
531
520
|
convertEntityListToResponseListDto(entities, _isRaw) {
|
|
532
521
|
return entities.map((entity)=>this.convertEntityToResponseDto(entity, _isRaw));
|
|
533
522
|
}
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
523
|
+
/**
|
|
524
|
+
* `entity` is the table this service reads and writes. A service whose table
|
|
525
|
+
* depends on runtime config - the company feature, say - passes the ternary
|
|
526
|
+
* here, reading the injected config through the constructor parameter.
|
|
527
|
+
*/ constructor(entityName, cacheManager, utilsService, _loggerName, isCacheable = false, moduleName, dataSourceProvider, entity){
|
|
538
528
|
_define_property(this, "entityName", void 0);
|
|
539
529
|
_define_property(this, "cacheManager", void 0);
|
|
540
530
|
_define_property(this, "utilsService", void 0);
|
|
@@ -552,8 +542,8 @@ let ApiService = class ApiService {
|
|
|
552
542
|
this.isCacheable = isCacheable;
|
|
553
543
|
this.moduleName = moduleName;
|
|
554
544
|
this.repositoryInitialized = false;
|
|
555
|
-
this._entity = entity;
|
|
556
545
|
this._dataSourceProvider = dataSourceProvider;
|
|
546
|
+
this._entity = entity;
|
|
557
547
|
}
|
|
558
548
|
};
|
|
559
549
|
_ts_decorate([
|
|
@@ -635,7 +625,7 @@ ApiService = _ts_decorate([
|
|
|
635
625
|
String,
|
|
636
626
|
Boolean,
|
|
637
627
|
String,
|
|
638
|
-
typeof
|
|
639
|
-
typeof
|
|
628
|
+
typeof _interfaces.IDataSourceProvider === "undefined" ? Object : _interfaces.IDataSourceProvider,
|
|
629
|
+
typeof _typeorm.EntityTarget === "undefined" ? Object : _typeorm.EntityTarget
|
|
640
630
|
])
|
|
641
631
|
], ApiService);
|