@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
|
@@ -1,13 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { EntityTarget, QueryRunner, Repository, SelectQueryBuilder } from 'typeorm';
|
|
2
2
|
import { DeleteDto, FilterAndPaginationDto } from '../dtos';
|
|
3
3
|
import { Identity, RawEntity } from '../entities';
|
|
4
|
-
import {
|
|
5
|
-
import { DomainEventActionName, IDataSourceProvider, ILoggedUserInfo, IService } from '../interfaces';
|
|
4
|
+
import { CrudEventActionName, IDataSourceProvider, ILoggedUserInfo, IService } from '../interfaces';
|
|
6
5
|
import { UtilsService } from '../modules/utils/utils.service';
|
|
7
6
|
import { IPublishDomainEventInput } from './domain-event.publisher';
|
|
8
7
|
import { HybridCache } from './hybrid-cache.class';
|
|
9
8
|
interface IEventGroup<T> {
|
|
10
|
-
action:
|
|
9
|
+
action: CrudEventActionName;
|
|
11
10
|
entities: T[];
|
|
12
11
|
idsOnly?: boolean;
|
|
13
12
|
}
|
|
@@ -19,17 +18,15 @@ export declare abstract class ApiService<CreateDtoT extends object, UpdateDtoT e
|
|
|
19
18
|
protected utilsService: UtilsService;
|
|
20
19
|
protected _loggerName: string;
|
|
21
20
|
protected isCacheable: boolean;
|
|
22
|
-
protected moduleName
|
|
21
|
+
protected moduleName: string;
|
|
23
22
|
protected repository: Repository<EntityT>;
|
|
24
23
|
private repositoryInitialized;
|
|
25
|
-
private _entity
|
|
26
|
-
private _dataSourceProvider
|
|
27
|
-
constructor(entityName: string, cacheManager: HybridCache, utilsService: UtilsService, _loggerName: string, isCacheable
|
|
28
|
-
protected resolveEntity(): EntityTarget<EntityT>;
|
|
24
|
+
private readonly _entity;
|
|
25
|
+
private readonly _dataSourceProvider;
|
|
26
|
+
constructor(entityName: string, cacheManager: HybridCache, utilsService: UtilsService, _loggerName: string, isCacheable: boolean | undefined, moduleName: string, dataSourceProvider: IDataSourceProvider, entity: EntityTarget<EntityT>);
|
|
29
27
|
protected getDataSourceProvider(): IDataSourceProvider;
|
|
30
28
|
protected ensureDataSourceRepository(): Promise<void>;
|
|
31
29
|
protected resolveRepositories(entities: EntityTarget<any>[]): Promise<Repository<any>[]>;
|
|
32
|
-
protected getDataSourceForService(): Promise<DataSource>;
|
|
33
30
|
insert(dto: CreateDtoT, user: ILoggedUserInfo | null): Promise<InterfaceT>;
|
|
34
31
|
insertMany(dtos: Array<CreateDtoT>, user: ILoggedUserInfo | null): Promise<InterfaceT[]>;
|
|
35
32
|
update(dto: UpdateDtoT, user: ILoggedUserInfo | null): Promise<InterfaceT>;
|
|
@@ -43,7 +40,7 @@ export declare abstract class ApiService<CreateDtoT extends object, UpdateDtoT e
|
|
|
43
40
|
}>;
|
|
44
41
|
delete(option: DeleteDto, user: ILoggedUserInfo | null): Promise<null>;
|
|
45
42
|
protected publishLifecycleEvents(groups: IEventGroup<EntityT>[], user: ILoggedUserInfo | null): Promise<void>;
|
|
46
|
-
protected publishDomainAction(action:
|
|
43
|
+
protected publishDomainAction(action: string, input?: Omit<IPublishDomainEventInput, 'module' | 'entity' | 'action'>): Promise<void>;
|
|
47
44
|
clearCacheForAll(): Promise<void>;
|
|
48
45
|
clearCacheForId(entities: EntityT[]): Promise<void>;
|
|
49
46
|
private handleError;
|
|
@@ -82,6 +79,5 @@ export declare abstract class ApiService<CreateDtoT extends object, UpdateDtoT e
|
|
|
82
79
|
protected convertArrayDtoToEntities(dtos: Array<CreateDtoT | UpdateDtoT>, user: ILoggedUserInfo | null): Promise<Array<EntityT>>;
|
|
83
80
|
protected convertEntityToResponseDto(entity: EntityT | RawEntity, _isRaw: boolean): InterfaceT;
|
|
84
81
|
protected convertEntityListToResponseListDto(entities: EntityT[] | RawEntity[], _isRaw: boolean): InterfaceT[];
|
|
85
|
-
protected getEntityClass(): Promise<EntityT>;
|
|
86
82
|
}
|
|
87
83
|
export {};
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { IDomainEvent, IDomainEventPayload, IEventBus, ILoggedUserInfo } from '../interfaces';
|
|
2
2
|
export declare const DOMAIN_EVENT_VERSION = 1;
|
|
3
3
|
export interface IPublishDomainEventInput<T = Record<string, unknown>> {
|
|
4
4
|
module: string;
|
|
5
5
|
entity: string;
|
|
6
|
-
action:
|
|
6
|
+
action: string;
|
|
7
7
|
ids?: string[];
|
|
8
8
|
data?: T | T[];
|
|
9
9
|
metadata?: Record<string, unknown>;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { IEventBus, IModuleEventsConfig } from '../interfaces/event-bus.interface';
|
|
2
2
|
export declare class EventBusRegistry {
|
|
3
3
|
private static bus?;
|
|
4
4
|
private static moduleConfigs;
|
|
@@ -8,7 +8,7 @@ export declare class EventBusRegistry {
|
|
|
8
8
|
static setDefaultModuleEvents(config: IModuleEventsConfig): void;
|
|
9
9
|
static configureModule(moduleName: string, config?: IModuleEventsConfig): void;
|
|
10
10
|
static getModuleConfig(moduleName?: string): IModuleEventsConfig;
|
|
11
|
-
static isEnabled(moduleName: string | undefined, entity: string, action:
|
|
11
|
+
static isEnabled(moduleName: string | undefined, entity: string, action: string): boolean;
|
|
12
12
|
static shouldIncludePayload(moduleName?: string): boolean;
|
|
13
13
|
static reset(): void;
|
|
14
14
|
}
|
|
@@ -3,7 +3,7 @@ export declare class MemoryTransport implements IEventTransport {
|
|
|
3
3
|
readonly name = "memory";
|
|
4
4
|
private readonly emitter;
|
|
5
5
|
private ready;
|
|
6
|
-
constructor(
|
|
6
|
+
constructor();
|
|
7
7
|
connect(): Promise<void>;
|
|
8
8
|
publish(event: IDomainEvent): Promise<void>;
|
|
9
9
|
bind(_pattern: string): Promise<void>;
|
|
@@ -4,6 +4,7 @@ export declare class HybridEventBus implements IEventBus {
|
|
|
4
4
|
private readonly logger;
|
|
5
5
|
private readonly subscriptions;
|
|
6
6
|
private readonly transports;
|
|
7
|
+
private readonly boundPatterns;
|
|
7
8
|
private readonly seenIds;
|
|
8
9
|
private readonly seenOrder;
|
|
9
10
|
private readonly label;
|
|
@@ -21,6 +22,7 @@ export declare class HybridEventBus implements IEventBus {
|
|
|
21
22
|
private ensureConnected;
|
|
22
23
|
private resolveLabelFromEnv;
|
|
23
24
|
private buildTransports;
|
|
25
|
+
private bindSubscriptions;
|
|
24
26
|
private createBrokerTransport;
|
|
25
27
|
private resolveHybridBrokerName;
|
|
26
28
|
private loadOptionalTransport;
|
package/enums/index.d.ts
CHANGED
|
@@ -25,35 +25,23 @@ import { EntityTarget, In } from 'typeorm';
|
|
|
25
25
|
import { ERROR_MESSAGES, SYSTEM_MESSAGES } from '../constants';
|
|
26
26
|
import { LogAction } from '../decorators/log-action.decorator';
|
|
27
27
|
import { DeleteDto } from '../dtos';
|
|
28
|
-
import { EventAction } from '../enums/event-action.enum';
|
|
29
28
|
import { IDataSourceProvider } from '../interfaces';
|
|
30
29
|
import { UtilsService } from '../modules/utils/utils.service';
|
|
31
30
|
import { publishDomainEvent } from './domain-event.publisher';
|
|
32
31
|
import { HybridCache } from './hybrid-cache.class';
|
|
33
|
-
const DEFAULT_EVENT_MODULE = 'app';
|
|
34
|
-
const OPERATION_EVENT_ACTIONS = {
|
|
35
|
-
insert: EventAction.CREATED,
|
|
36
|
-
insertMany: EventAction.CREATED,
|
|
37
|
-
update: EventAction.UPDATED,
|
|
38
|
-
updateMany: EventAction.UPDATED,
|
|
39
|
-
bulkUpsert: EventAction.UPDATED
|
|
40
|
-
};
|
|
41
32
|
const DELETE_EVENT_ACTIONS = {
|
|
42
|
-
delete:
|
|
43
|
-
restore:
|
|
33
|
+
delete: 'deleted',
|
|
34
|
+
restore: 'restored',
|
|
35
|
+
permanent: 'purged'
|
|
44
36
|
};
|
|
45
37
|
export class ApiService {
|
|
46
|
-
resolveEntity() {
|
|
47
|
-
return this._entity;
|
|
48
|
-
}
|
|
49
38
|
getDataSourceProvider() {
|
|
50
39
|
return this._dataSourceProvider;
|
|
51
40
|
}
|
|
52
41
|
async ensureDataSourceRepository() {
|
|
53
42
|
if (!this.repositoryInitialized) {
|
|
54
|
-
const entity = this.resolveEntity();
|
|
55
43
|
const provider = this.getDataSourceProvider();
|
|
56
|
-
this.repository = await provider.getRepository(
|
|
44
|
+
this.repository = await provider.getRepository(this._entity);
|
|
57
45
|
this.repositoryInitialized = true;
|
|
58
46
|
}
|
|
59
47
|
}
|
|
@@ -65,11 +53,8 @@ export class ApiService {
|
|
|
65
53
|
}
|
|
66
54
|
return repositories;
|
|
67
55
|
}
|
|
68
|
-
async getDataSourceForService() {
|
|
69
|
-
return this.getDataSourceProvider().getDataSource();
|
|
70
|
-
}
|
|
71
56
|
async insert(dto, user) {
|
|
72
|
-
return this.executeInTransaction('
|
|
57
|
+
return this.executeInTransaction('created', async (qr)=>{
|
|
73
58
|
await this.beforeInsertOperation(dto, user, qr);
|
|
74
59
|
const entities = await this.convertRequestDtoToEntity(dto, user);
|
|
75
60
|
const saved = await qr.manager.save(this.repository.target, entities);
|
|
@@ -81,7 +66,7 @@ export class ApiService {
|
|
|
81
66
|
}, user);
|
|
82
67
|
}
|
|
83
68
|
async insertMany(dtos, user) {
|
|
84
|
-
return this.executeInTransaction('
|
|
69
|
+
return this.executeInTransaction('created', async (qr)=>{
|
|
85
70
|
await this.beforeInsertOperation(dtos, user, qr);
|
|
86
71
|
const entities = await this.convertRequestDtoToEntity(dtos, user);
|
|
87
72
|
const saved = await qr.manager.save(this.repository.target, entities);
|
|
@@ -93,7 +78,7 @@ export class ApiService {
|
|
|
93
78
|
}, user);
|
|
94
79
|
}
|
|
95
80
|
async update(dto, user) {
|
|
96
|
-
return this.executeInTransaction('
|
|
81
|
+
return this.executeInTransaction('updated', async (qr)=>{
|
|
97
82
|
await this.beforeUpdateOperation(dto, user, qr);
|
|
98
83
|
const entities = await this.convertRequestDtoToEntity(dto, user);
|
|
99
84
|
const saved = await qr.manager.save(this.repository.target, entities);
|
|
@@ -105,7 +90,7 @@ export class ApiService {
|
|
|
105
90
|
}, user);
|
|
106
91
|
}
|
|
107
92
|
async updateMany(dtos, user) {
|
|
108
|
-
return this.executeInTransaction('
|
|
93
|
+
return this.executeInTransaction('updated', async (qr)=>{
|
|
109
94
|
await this.beforeUpdateOperation(dtos, user, qr);
|
|
110
95
|
const entities = await this.convertRequestDtoToEntity(dtos, user);
|
|
111
96
|
const saved = await qr.manager.save(this.repository.target, entities);
|
|
@@ -117,7 +102,7 @@ export class ApiService {
|
|
|
117
102
|
}, user);
|
|
118
103
|
}
|
|
119
104
|
async bulkUpsert(toInsert, toUpdate, user) {
|
|
120
|
-
return this.executeInTransaction(
|
|
105
|
+
return this.executeInTransaction(undefined, async (qr)=>{
|
|
121
106
|
const allResults = [];
|
|
122
107
|
const eventGroups = [];
|
|
123
108
|
if (toInsert.length > 0) {
|
|
@@ -127,7 +112,7 @@ export class ApiService {
|
|
|
127
112
|
await this.afterInsertOperation(this.ensureArray(insertedSaved), toInsert, user, qr);
|
|
128
113
|
allResults.push(...this.ensureArray(insertedSaved));
|
|
129
114
|
eventGroups.push({
|
|
130
|
-
action:
|
|
115
|
+
action: 'created',
|
|
131
116
|
entities: this.ensureArray(insertedSaved)
|
|
132
117
|
});
|
|
133
118
|
}
|
|
@@ -138,7 +123,7 @@ export class ApiService {
|
|
|
138
123
|
await this.afterUpdateOperation(this.ensureArray(updatedSaved), toUpdate, user, qr);
|
|
139
124
|
allResults.push(...this.ensureArray(updatedSaved));
|
|
140
125
|
eventGroups.push({
|
|
141
|
-
action:
|
|
126
|
+
action: 'updated',
|
|
142
127
|
entities: this.ensureArray(updatedSaved)
|
|
143
128
|
});
|
|
144
129
|
}
|
|
@@ -174,7 +159,7 @@ export class ApiService {
|
|
|
174
159
|
if (error instanceof NotFoundException) {
|
|
175
160
|
throw error;
|
|
176
161
|
}
|
|
177
|
-
this.handleError(error
|
|
162
|
+
this.handleError(error);
|
|
178
163
|
}
|
|
179
164
|
}
|
|
180
165
|
async findById(id, user, select) {
|
|
@@ -214,7 +199,7 @@ export class ApiService {
|
|
|
214
199
|
if (error instanceof NotFoundException) {
|
|
215
200
|
throw error;
|
|
216
201
|
}
|
|
217
|
-
this.handleError(error
|
|
202
|
+
this.handleError(error);
|
|
218
203
|
}
|
|
219
204
|
}
|
|
220
205
|
async getAll(search, filterAndPaginationDto, user) {
|
|
@@ -313,7 +298,7 @@ export class ApiService {
|
|
|
313
298
|
}
|
|
314
299
|
return finalResponse;
|
|
315
300
|
} catch (error) {
|
|
316
|
-
this.handleError(error
|
|
301
|
+
this.handleError(error);
|
|
317
302
|
}
|
|
318
303
|
}
|
|
319
304
|
async delete(option, user) {
|
|
@@ -351,7 +336,7 @@ export class ApiService {
|
|
|
351
336
|
]);
|
|
352
337
|
await this.publishLifecycleEvents([
|
|
353
338
|
{
|
|
354
|
-
action: DELETE_EVENT_ACTIONS[option.type]
|
|
339
|
+
action: DELETE_EVENT_ACTIONS[option.type],
|
|
355
340
|
entities: ids,
|
|
356
341
|
idsOnly: true
|
|
357
342
|
}
|
|
@@ -359,7 +344,7 @@ export class ApiService {
|
|
|
359
344
|
return null;
|
|
360
345
|
} catch (error) {
|
|
361
346
|
await queryRunner.rollbackTransaction();
|
|
362
|
-
this.handleError(error
|
|
347
|
+
this.handleError(error);
|
|
363
348
|
} finally{
|
|
364
349
|
await queryRunner.release();
|
|
365
350
|
}
|
|
@@ -368,7 +353,7 @@ export class ApiService {
|
|
|
368
353
|
for (const group of groups){
|
|
369
354
|
if (!group.entities.length) continue;
|
|
370
355
|
await publishDomainEvent({
|
|
371
|
-
module: this.moduleName
|
|
356
|
+
module: this.moduleName,
|
|
372
357
|
entity: this.entityName,
|
|
373
358
|
action: group.action,
|
|
374
359
|
ids: group.entities.map((entity)=>entity.id),
|
|
@@ -385,7 +370,7 @@ export class ApiService {
|
|
|
385
370
|
*/ async publishDomainAction(action, input = {}) {
|
|
386
371
|
await publishDomainEvent({
|
|
387
372
|
...input,
|
|
388
|
-
module: this.moduleName
|
|
373
|
+
module: this.moduleName,
|
|
389
374
|
entity: this.entityName,
|
|
390
375
|
action
|
|
391
376
|
});
|
|
@@ -396,7 +381,7 @@ export class ApiService {
|
|
|
396
381
|
async clearCacheForId(entities) {
|
|
397
382
|
await Promise.all(entities.map((item)=>this.utilsService.clearCache(this.entityName, this.cacheManager, item.id)));
|
|
398
383
|
}
|
|
399
|
-
handleError(error
|
|
384
|
+
handleError(error) {
|
|
400
385
|
if (error instanceof Error) {
|
|
401
386
|
throw error;
|
|
402
387
|
}
|
|
@@ -410,7 +395,11 @@ export class ApiService {
|
|
|
410
395
|
value
|
|
411
396
|
];
|
|
412
397
|
}
|
|
413
|
-
|
|
398
|
+
/**
|
|
399
|
+
* `action` is the lifecycle action to publish for the whole batch; an
|
|
400
|
+
* operation that saves more than one kind of row - a bulk upsert - passes
|
|
401
|
+
* `undefined` and returns its own `eventGroups` instead.
|
|
402
|
+
*/ async executeInTransaction(action, fn, user) {
|
|
414
403
|
await this.ensureDataSourceRepository();
|
|
415
404
|
const qr = this.repository.manager.connection.createQueryRunner();
|
|
416
405
|
await qr.connect();
|
|
@@ -425,19 +414,19 @@ export class ApiService {
|
|
|
425
414
|
this.clearCacheForId(savedArray)
|
|
426
415
|
]);
|
|
427
416
|
}
|
|
428
|
-
await this.publishLifecycleEvents(eventGroups ?? [
|
|
417
|
+
await this.publishLifecycleEvents(eventGroups ?? (action ? [
|
|
429
418
|
{
|
|
430
|
-
action
|
|
419
|
+
action,
|
|
431
420
|
entities: savedArray
|
|
432
421
|
}
|
|
433
|
-
], user ?? null);
|
|
422
|
+
] : []), user ?? null);
|
|
434
423
|
if (returnFirst) {
|
|
435
424
|
return this.convertEntityToResponseDto(savedArray[0], false);
|
|
436
425
|
}
|
|
437
426
|
return savedArray.map((e)=>this.convertEntityToResponseDto(e, false));
|
|
438
427
|
} catch (error) {
|
|
439
428
|
await qr.rollbackTransaction();
|
|
440
|
-
this.handleError(error
|
|
429
|
+
this.handleError(error);
|
|
441
430
|
} finally{
|
|
442
431
|
await qr.release();
|
|
443
432
|
}
|
|
@@ -510,7 +499,7 @@ export class ApiService {
|
|
|
510
499
|
return Object.assign(existing, d);
|
|
511
500
|
}
|
|
512
501
|
delete d.id;
|
|
513
|
-
return Object.assign(
|
|
502
|
+
return Object.assign(this.repository.create(), d);
|
|
514
503
|
}
|
|
515
504
|
async convertArrayDtoToEntities(dtos, user) {
|
|
516
505
|
return await Promise.all(dtos.map((dto)=>this.convertSingleDtoToEntity(dto, user)));
|
|
@@ -521,10 +510,11 @@ export class ApiService {
|
|
|
521
510
|
convertEntityListToResponseListDto(entities, _isRaw) {
|
|
522
511
|
return entities.map((entity)=>this.convertEntityToResponseDto(entity, _isRaw));
|
|
523
512
|
}
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
513
|
+
/**
|
|
514
|
+
* `entity` is the table this service reads and writes. A service whose table
|
|
515
|
+
* depends on runtime config - the company feature, say - passes the ternary
|
|
516
|
+
* here, reading the injected config through the constructor parameter.
|
|
517
|
+
*/ constructor(entityName, cacheManager, utilsService, _loggerName, isCacheable = false, moduleName, dataSourceProvider, entity){
|
|
528
518
|
_define_property(this, "entityName", void 0);
|
|
529
519
|
_define_property(this, "cacheManager", void 0);
|
|
530
520
|
_define_property(this, "utilsService", void 0);
|
|
@@ -542,8 +532,8 @@ export class ApiService {
|
|
|
542
532
|
this.isCacheable = isCacheable;
|
|
543
533
|
this.moduleName = moduleName;
|
|
544
534
|
this.repositoryInitialized = false;
|
|
545
|
-
this._entity = entity;
|
|
546
535
|
this._dataSourceProvider = dataSourceProvider;
|
|
536
|
+
this._entity = entity;
|
|
547
537
|
}
|
|
548
538
|
}
|
|
549
539
|
_ts_decorate([
|
|
@@ -625,7 +615,7 @@ ApiService = _ts_decorate([
|
|
|
625
615
|
String,
|
|
626
616
|
Boolean,
|
|
627
617
|
String,
|
|
628
|
-
typeof
|
|
629
|
-
typeof
|
|
618
|
+
typeof IDataSourceProvider === "undefined" ? Object : IDataSourceProvider,
|
|
619
|
+
typeof EntityTarget === "undefined" ? Object : EntityTarget
|
|
630
620
|
])
|
|
631
621
|
], ApiService);
|
|
@@ -16,7 +16,6 @@ import { createMockRepository, createMockDataSourceProvider } from '@test-utils/
|
|
|
16
16
|
import { buildMockUser } from '@test-utils/mocks/logged-user.mock';
|
|
17
17
|
import { ApiService } from './api-service.class';
|
|
18
18
|
import { EventBusRegistry } from './event-bus.registry';
|
|
19
|
-
import { EventAction } from '../enums/event-action.enum';
|
|
20
19
|
import { UtilsService } from '../modules/utils/utils.service';
|
|
21
20
|
import { Identity } from '../entities';
|
|
22
21
|
let TestEntity = class TestEntity extends Identity {
|
|
@@ -25,6 +24,14 @@ let TestEntity = class TestEntity extends Identity {
|
|
|
25
24
|
}
|
|
26
25
|
};
|
|
27
26
|
let TestApiService = class TestApiService extends ApiService {
|
|
27
|
+
/** `publishDomainAction` is protected - feature services reach it as subclasses do */ publishAction(action, metadata) {
|
|
28
|
+
return this.publishDomainAction(action, {
|
|
29
|
+
ids: [
|
|
30
|
+
'1'
|
|
31
|
+
],
|
|
32
|
+
metadata
|
|
33
|
+
});
|
|
34
|
+
}
|
|
28
35
|
};
|
|
29
36
|
function createMockQueryRunner() {
|
|
30
37
|
return {
|
|
@@ -52,7 +59,7 @@ describe('ApiService', ()=>{
|
|
|
52
59
|
let errorLogSpy;
|
|
53
60
|
function makeService(isCacheable = false) {
|
|
54
61
|
const dataSourceProvider = createMockDataSourceProvider(mockRepo);
|
|
55
|
-
return new TestApiService('test-entity', mockCache, utilsService, 'TestApiService', isCacheable,
|
|
62
|
+
return new TestApiService('test-entity', mockCache, utilsService, 'TestApiService', isCacheable, 'app', dataSourceProvider, TestEntity);
|
|
56
63
|
}
|
|
57
64
|
beforeEach(()=>{
|
|
58
65
|
mockRepo = createMockRepository();
|
|
@@ -641,7 +648,7 @@ describe('ApiService', ()=>{
|
|
|
641
648
|
function makeRawService(dbType) {
|
|
642
649
|
mockRepo.manager.connection.options.type = dbType;
|
|
643
650
|
const dataSourceProvider = createMockDataSourceProvider(mockRepo);
|
|
644
|
-
return new RawTestApiService('test-entity', mockCache, utilsService, 'RawTestApiService', false,
|
|
651
|
+
return new RawTestApiService('test-entity', mockCache, utilsService, 'RawTestApiService', false, 'app', dataSourceProvider, TestEntity);
|
|
645
652
|
}
|
|
646
653
|
function primeRawQueryBuilder(qb) {
|
|
647
654
|
qb.expressionMap = {
|
|
@@ -808,7 +815,7 @@ describe('ApiService', ()=>{
|
|
|
808
815
|
let publish;
|
|
809
816
|
function makeEventService(moduleName = 'test-module') {
|
|
810
817
|
const dataSourceProvider = createMockDataSourceProvider(mockRepo);
|
|
811
|
-
return new TestApiService('test-entity', mockCache, utilsService, 'TestApiService', false, moduleName,
|
|
818
|
+
return new TestApiService('test-entity', mockCache, utilsService, 'TestApiService', false, moduleName, dataSourceProvider, TestEntity);
|
|
812
819
|
}
|
|
813
820
|
beforeEach(()=>{
|
|
814
821
|
publish = jest.fn().mockResolvedValue(undefined);
|
|
@@ -839,7 +846,7 @@ describe('ApiService', ()=>{
|
|
|
839
846
|
name: 'test-module.test-entity.created',
|
|
840
847
|
module: 'test-module',
|
|
841
848
|
entity: 'test-entity',
|
|
842
|
-
action:
|
|
849
|
+
action: 'created',
|
|
843
850
|
payload: expect.objectContaining({
|
|
844
851
|
ids: [
|
|
845
852
|
'new-1'
|
|
@@ -860,6 +867,74 @@ describe('ApiService', ()=>{
|
|
|
860
867
|
userId: mockUser.id
|
|
861
868
|
}));
|
|
862
869
|
});
|
|
870
|
+
it('should publish one created event carrying every id for insertMany', async ()=>{
|
|
871
|
+
const service = makeEventService();
|
|
872
|
+
mockQueryRunner.manager.save.mockResolvedValue([
|
|
873
|
+
{
|
|
874
|
+
id: 'new-1',
|
|
875
|
+
name: 'A'
|
|
876
|
+
},
|
|
877
|
+
{
|
|
878
|
+
id: 'new-2',
|
|
879
|
+
name: 'B'
|
|
880
|
+
}
|
|
881
|
+
]);
|
|
882
|
+
await service.insertMany([
|
|
883
|
+
{
|
|
884
|
+
name: 'A'
|
|
885
|
+
},
|
|
886
|
+
{
|
|
887
|
+
name: 'B'
|
|
888
|
+
}
|
|
889
|
+
], mockUser);
|
|
890
|
+
expect(publish).toHaveBeenCalledTimes(1);
|
|
891
|
+
expect(publish.mock.calls[0][0]).toEqual(expect.objectContaining({
|
|
892
|
+
action: 'created',
|
|
893
|
+
payload: expect.objectContaining({
|
|
894
|
+
ids: [
|
|
895
|
+
'new-1',
|
|
896
|
+
'new-2'
|
|
897
|
+
]
|
|
898
|
+
})
|
|
899
|
+
}));
|
|
900
|
+
});
|
|
901
|
+
it('should publish one updated event carrying every id for updateMany', async ()=>{
|
|
902
|
+
const service = makeEventService();
|
|
903
|
+
mockRepo.findOne.mockResolvedValue({
|
|
904
|
+
id: '1',
|
|
905
|
+
name: 'Old'
|
|
906
|
+
});
|
|
907
|
+
mockQueryRunner.manager.save.mockResolvedValue([
|
|
908
|
+
{
|
|
909
|
+
id: '1',
|
|
910
|
+
name: 'A'
|
|
911
|
+
},
|
|
912
|
+
{
|
|
913
|
+
id: '2',
|
|
914
|
+
name: 'B'
|
|
915
|
+
}
|
|
916
|
+
]);
|
|
917
|
+
await service.updateMany([
|
|
918
|
+
{
|
|
919
|
+
id: '1',
|
|
920
|
+
name: 'A'
|
|
921
|
+
},
|
|
922
|
+
{
|
|
923
|
+
id: '2',
|
|
924
|
+
name: 'B'
|
|
925
|
+
}
|
|
926
|
+
], mockUser);
|
|
927
|
+
expect(publish).toHaveBeenCalledTimes(1);
|
|
928
|
+
expect(publish.mock.calls[0][0]).toEqual(expect.objectContaining({
|
|
929
|
+
action: 'updated',
|
|
930
|
+
payload: expect.objectContaining({
|
|
931
|
+
ids: [
|
|
932
|
+
'1',
|
|
933
|
+
'2'
|
|
934
|
+
]
|
|
935
|
+
})
|
|
936
|
+
}));
|
|
937
|
+
});
|
|
863
938
|
it('should publish an updated event after update commits', async ()=>{
|
|
864
939
|
const service = makeEventService();
|
|
865
940
|
mockRepo.findOne.mockResolvedValue({
|
|
@@ -875,7 +950,7 @@ describe('ApiService', ()=>{
|
|
|
875
950
|
name: 'New'
|
|
876
951
|
}, mockUser);
|
|
877
952
|
expect(publish.mock.calls[0][0]).toEqual(expect.objectContaining({
|
|
878
|
-
action:
|
|
953
|
+
action: 'updated'
|
|
879
954
|
}));
|
|
880
955
|
});
|
|
881
956
|
it('should publish created and updated separately for a bulk upsert', async ()=>{
|
|
@@ -906,22 +981,22 @@ describe('ApiService', ()=>{
|
|
|
906
981
|
}
|
|
907
982
|
], mockUser);
|
|
908
983
|
expect(publish.mock.calls.map(([event])=>event.action)).toEqual([
|
|
909
|
-
|
|
910
|
-
|
|
984
|
+
'created',
|
|
985
|
+
'updated'
|
|
911
986
|
]);
|
|
912
987
|
});
|
|
913
988
|
it.each([
|
|
914
989
|
[
|
|
915
990
|
'delete',
|
|
916
|
-
|
|
991
|
+
'deleted'
|
|
917
992
|
],
|
|
918
993
|
[
|
|
919
994
|
'restore',
|
|
920
|
-
|
|
995
|
+
'restored'
|
|
921
996
|
],
|
|
922
997
|
[
|
|
923
998
|
'permanent',
|
|
924
|
-
|
|
999
|
+
'purged'
|
|
925
1000
|
]
|
|
926
1001
|
])('should publish %s as the %s action', async (type, action)=>{
|
|
927
1002
|
const service = makeEventService();
|
|
@@ -945,6 +1020,39 @@ describe('ApiService', ()=>{
|
|
|
945
1020
|
]
|
|
946
1021
|
});
|
|
947
1022
|
});
|
|
1023
|
+
it('should publish a domain action under this service module and entity', async ()=>{
|
|
1024
|
+
const service = makeEventService();
|
|
1025
|
+
await service.publishAction('submitted', {
|
|
1026
|
+
formId: 'form-1'
|
|
1027
|
+
});
|
|
1028
|
+
expect(publish.mock.calls[0][0]).toEqual(expect.objectContaining({
|
|
1029
|
+
name: 'test-module.test-entity.submitted',
|
|
1030
|
+
module: 'test-module',
|
|
1031
|
+
entity: 'test-entity',
|
|
1032
|
+
action: 'submitted',
|
|
1033
|
+
payload: {
|
|
1034
|
+
ids: [
|
|
1035
|
+
'1'
|
|
1036
|
+
],
|
|
1037
|
+
metadata: {
|
|
1038
|
+
formId: 'form-1'
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
}));
|
|
1042
|
+
});
|
|
1043
|
+
it('should hold a domain action to the same allowlist as the CRUD actions', async ()=>{
|
|
1044
|
+
EventBusRegistry.configureModule('test-module', {
|
|
1045
|
+
enabled: true,
|
|
1046
|
+
actions: [
|
|
1047
|
+
'test-entity.submitted'
|
|
1048
|
+
]
|
|
1049
|
+
});
|
|
1050
|
+
const service = makeEventService();
|
|
1051
|
+
await service.publishAction('submitted');
|
|
1052
|
+
await service.publishAction('draft-saved');
|
|
1053
|
+
expect(publish).toHaveBeenCalledTimes(1);
|
|
1054
|
+
expect(publish.mock.calls[0][0].action).toBe('submitted');
|
|
1055
|
+
});
|
|
948
1056
|
it('should not publish when the module has events disabled', async ()=>{
|
|
949
1057
|
EventBusRegistry.configureModule('test-module', {
|
|
950
1058
|
enabled: false
|
|
@@ -963,7 +1071,7 @@ describe('ApiService', ()=>{
|
|
|
963
1071
|
EventBusRegistry.configureModule('test-module', {
|
|
964
1072
|
enabled: true,
|
|
965
1073
|
actions: [
|
|
966
|
-
|
|
1074
|
+
'deleted'
|
|
967
1075
|
]
|
|
968
1076
|
});
|
|
969
1077
|
const service = makeEventService();
|
|
@@ -11,6 +11,7 @@ function _define_property(obj, key, value) {
|
|
|
11
11
|
}
|
|
12
12
|
return obj;
|
|
13
13
|
}
|
|
14
|
+
import { matchEventPattern } from '../utils/event-pattern.util';
|
|
14
15
|
const DISABLED = {
|
|
15
16
|
enabled: false
|
|
16
17
|
};
|
|
@@ -44,12 +45,10 @@ const DISABLED = {
|
|
|
44
45
|
*/ static isEnabled(moduleName, entity, action) {
|
|
45
46
|
const config = this.getModuleConfig(moduleName);
|
|
46
47
|
if (!config.enabled) return false;
|
|
47
|
-
if (config.excludeEntities?.includes(entity)) return false;
|
|
48
|
-
if (config.entities?.length && !config.entities.includes(entity)) return false;
|
|
49
48
|
// Without an explicit allowlist, CRUD lifecycle actions and every domain
|
|
50
49
|
// action a package defines are all published.
|
|
51
50
|
if (!config.actions?.length) return true;
|
|
52
|
-
return config.actions.includes(action);
|
|
51
|
+
return config.actions.some((entry)=>entry.includes('.') ? matchEventPattern(entry, `${entity}.${action}`) : matchEventPattern(entry, action));
|
|
53
52
|
}
|
|
54
53
|
static shouldIncludePayload(moduleName) {
|
|
55
54
|
return this.getModuleConfig(moduleName).includePayload ?? true;
|