@flusys/nestjs-shared 6.4.1 → 7.0.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.
- package/README.md +437 -0
- package/cjs/classes/api-service.class.js +71 -8
- package/cjs/classes/api-service.class.spec.js +220 -0
- package/cjs/classes/disabled-event-bus.class.js +15 -0
- package/cjs/classes/disabled-event-bus.class.spec.js +40 -0
- package/cjs/classes/domain-event.publisher.js +76 -0
- package/cjs/classes/domain-event.publisher.spec.js +234 -0
- package/cjs/classes/event-bus.registry.js +69 -0
- package/cjs/classes/event-bus.registry.spec.js +116 -0
- package/cjs/classes/event-transports/event-transport.registry.js +41 -0
- package/cjs/classes/event-transports/event-transport.registry.spec.js +31 -0
- package/cjs/classes/event-transports/index.js +23 -0
- package/cjs/classes/event-transports/kafka-transport.optional.js +151 -0
- package/cjs/classes/event-transports/kafka-transport.optional.spec.js +199 -0
- package/cjs/classes/event-transports/memory-transport.js +54 -0
- package/cjs/classes/event-transports/memory-transport.spec.js +58 -0
- package/cjs/classes/event-transports/rabbitmq-transport.optional.js +184 -0
- package/cjs/classes/event-transports/rabbitmq-transport.optional.spec.js +256 -0
- package/cjs/classes/hybrid-event-bus.class.js +261 -0
- package/cjs/classes/hybrid-event-bus.class.spec.js +189 -0
- package/cjs/classes/index.js +5 -0
- package/cjs/constants/index.js +24 -0
- package/cjs/decorators/index.js +1 -0
- package/cjs/decorators/on-domain-event.decorator.js +13 -0
- package/cjs/decorators/on-domain-event.decorator.spec.js +50 -0
- package/cjs/entities/user-root.js +2 -2
- package/cjs/enums/event-action.enum.js +18 -0
- package/cjs/enums/index.js +1 -0
- package/cjs/interfaces/event-bus.interface.js +4 -0
- package/cjs/interfaces/index.js +1 -0
- package/cjs/modules/event-bus/event-bus.explorer.js +124 -0
- package/cjs/modules/event-bus/event-bus.explorer.spec.js +189 -0
- package/cjs/modules/event-bus/event-bus.integration.spec.js +254 -0
- package/cjs/modules/event-bus/event-bus.module.js +94 -0
- package/cjs/modules/event-bus/event-bus.module.spec.js +149 -0
- package/cjs/modules/event-bus/module-events.provider.js +39 -0
- package/cjs/modules/event-bus/module-events.provider.spec.js +56 -0
- package/cjs/modules/index.js +3 -0
- package/cjs/utils/event-pattern.util.js +98 -0
- package/cjs/utils/event-pattern.util.spec.js +234 -0
- package/cjs/utils/index.js +2 -0
- package/cjs/utils/optional-module.util.js +35 -0
- package/cjs/utils/optional-module.util.spec.js +14 -0
- package/classes/api-service.class.d.ts +11 -1
- package/classes/disabled-event-bus.class.d.ts +6 -0
- package/classes/domain-event.publisher.d.ts +14 -0
- package/classes/event-bus.registry.d.ts +14 -0
- package/classes/event-transports/event-transport.registry.d.ts +10 -0
- package/classes/event-transports/index.d.ts +2 -0
- package/classes/event-transports/kafka-transport.optional.d.ts +21 -0
- package/classes/event-transports/memory-transport.d.ts +13 -0
- package/classes/event-transports/rabbitmq-transport.optional.d.ts +27 -0
- package/classes/hybrid-event-bus.class.d.ts +32 -0
- package/classes/index.d.ts +5 -0
- package/constants/index.d.ts +6 -0
- package/decorators/index.d.ts +1 -0
- package/decorators/on-domain-event.decorator.d.ts +1 -0
- package/enums/event-action.enum.d.ts +7 -0
- package/enums/index.d.ts +1 -0
- package/fesm/classes/api-service.class.js +71 -8
- package/fesm/classes/api-service.class.spec.js +220 -0
- package/fesm/classes/disabled-event-bus.class.js +11 -0
- package/fesm/classes/disabled-event-bus.class.spec.js +36 -0
- package/fesm/classes/domain-event.publisher.js +63 -0
- package/fesm/classes/domain-event.publisher.spec.js +230 -0
- package/fesm/classes/event-bus.registry.js +65 -0
- package/fesm/classes/event-bus.registry.spec.js +112 -0
- package/fesm/classes/event-transports/event-transport.registry.js +35 -0
- package/fesm/classes/event-transports/event-transport.registry.spec.js +27 -0
- package/fesm/classes/event-transports/index.js +6 -0
- package/fesm/classes/event-transports/kafka-transport.optional.js +144 -0
- package/fesm/classes/event-transports/kafka-transport.optional.spec.js +195 -0
- package/fesm/classes/event-transports/memory-transport.js +46 -0
- package/fesm/classes/event-transports/memory-transport.spec.js +54 -0
- package/fesm/classes/event-transports/rabbitmq-transport.optional.js +174 -0
- package/fesm/classes/event-transports/rabbitmq-transport.optional.spec.js +252 -0
- package/fesm/classes/hybrid-event-bus.class.js +217 -0
- package/fesm/classes/hybrid-event-bus.class.spec.js +185 -0
- package/fesm/classes/index.js +5 -0
- package/fesm/constants/index.js +7 -0
- package/fesm/decorators/index.js +1 -0
- package/fesm/decorators/on-domain-event.decorator.js +10 -0
- package/fesm/decorators/on-domain-event.decorator.spec.js +46 -0
- package/fesm/entities/user-root.js +1 -1
- package/fesm/enums/event-action.enum.js +8 -0
- package/fesm/enums/index.js +1 -0
- package/fesm/interfaces/event-bus.interface.js +1 -0
- package/fesm/interfaces/index.js +1 -0
- package/fesm/modules/event-bus/event-bus.explorer.js +114 -0
- package/fesm/modules/event-bus/event-bus.explorer.spec.js +185 -0
- package/fesm/modules/event-bus/event-bus.integration.spec.js +250 -0
- package/fesm/modules/event-bus/event-bus.module.js +84 -0
- package/fesm/modules/event-bus/event-bus.module.spec.js +145 -0
- package/fesm/modules/event-bus/module-events.provider.js +26 -0
- package/fesm/modules/event-bus/module-events.provider.spec.js +52 -0
- package/fesm/modules/index.js +3 -0
- package/fesm/utils/event-pattern.util.js +86 -0
- package/fesm/utils/event-pattern.util.spec.js +230 -0
- package/fesm/utils/index.js +2 -0
- package/fesm/utils/optional-module.util.js +25 -0
- package/fesm/utils/optional-module.util.spec.js +10 -0
- package/interfaces/event-bus.interface.d.ts +74 -0
- package/interfaces/index.d.ts +1 -0
- package/modules/event-bus/event-bus.explorer.d.ts +14 -0
- package/modules/event-bus/event-bus.module.d.ts +8 -0
- package/modules/event-bus/module-events.provider.d.ts +3 -0
- package/modules/index.d.ts +3 -0
- package/package.json +2 -2
- package/utils/event-pattern.util.d.ts +3 -0
- package/utils/index.d.ts +2 -0
- package/utils/optional-module.util.d.ts +1 -0
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", {
|
|
3
|
+
value: true
|
|
4
|
+
});
|
|
5
|
+
const _disabledeventbusclass = require("../../classes/disabled-event-bus.class");
|
|
6
|
+
const _eventbusregistry = require("../../classes/event-bus.registry");
|
|
7
|
+
const _hybrideventbusclass = require("../../classes/hybrid-event-bus.class");
|
|
8
|
+
const _constants = require("../../constants");
|
|
9
|
+
const _eventbusexplorer = require("./event-bus.explorer");
|
|
10
|
+
const _eventbusmodule = require("./event-bus.module");
|
|
11
|
+
function getBusProvider(providers) {
|
|
12
|
+
return providers.find((provider)=>provider.provide === _constants.EVENT_BUS_INSTANCE);
|
|
13
|
+
}
|
|
14
|
+
describe('EventBusModule', ()=>{
|
|
15
|
+
const originalFlag = process.env.ENABLE_DOMAIN_EVENTS;
|
|
16
|
+
afterEach(()=>{
|
|
17
|
+
_eventbusregistry.EventBusRegistry.reset();
|
|
18
|
+
if (originalFlag === undefined) {
|
|
19
|
+
delete process.env.ENABLE_DOMAIN_EVENTS;
|
|
20
|
+
} else {
|
|
21
|
+
process.env.ENABLE_DOMAIN_EVENTS = originalFlag;
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
describe('when enabled', ()=>{
|
|
25
|
+
it('should default to a global module', ()=>{
|
|
26
|
+
const dynamicModule = _eventbusmodule.EventBusModule.forRoot({
|
|
27
|
+
enabled: true
|
|
28
|
+
});
|
|
29
|
+
expect(dynamicModule.module).toBe(_eventbusmodule.EventBusModule);
|
|
30
|
+
expect(dynamicModule.global).toBe(true);
|
|
31
|
+
});
|
|
32
|
+
it('should respect global=false', ()=>{
|
|
33
|
+
expect(_eventbusmodule.EventBusModule.forRoot({
|
|
34
|
+
enabled: true,
|
|
35
|
+
global: false
|
|
36
|
+
}).global).toBe(false);
|
|
37
|
+
});
|
|
38
|
+
it('should export the bus instance and the module options', ()=>{
|
|
39
|
+
const dynamicModule = _eventbusmodule.EventBusModule.forRoot({
|
|
40
|
+
enabled: true
|
|
41
|
+
});
|
|
42
|
+
expect(dynamicModule.exports).toEqual([
|
|
43
|
+
_constants.EVENT_BUS_INSTANCE,
|
|
44
|
+
_constants.EVENT_BUS_MODULE_OPTIONS
|
|
45
|
+
]);
|
|
46
|
+
});
|
|
47
|
+
it('should register the explorer so handlers get bound', ()=>{
|
|
48
|
+
const dynamicModule = _eventbusmodule.EventBusModule.forRoot({
|
|
49
|
+
enabled: true
|
|
50
|
+
});
|
|
51
|
+
expect(dynamicModule.providers).toContain(_eventbusexplorer.EventBusExplorer);
|
|
52
|
+
});
|
|
53
|
+
it('should build a HybridEventBus and register it for ApiService', ()=>{
|
|
54
|
+
const dynamicModule = _eventbusmodule.EventBusModule.forRoot({
|
|
55
|
+
enabled: true,
|
|
56
|
+
transport: 'memory'
|
|
57
|
+
});
|
|
58
|
+
const bus = getBusProvider(dynamicModule.providers).useFactory();
|
|
59
|
+
expect(bus).toBeInstanceOf(_hybrideventbusclass.HybridEventBus);
|
|
60
|
+
expect(_eventbusregistry.EventBusRegistry.getBus()).toBe(bus);
|
|
61
|
+
});
|
|
62
|
+
it('should apply the default module events config', ()=>{
|
|
63
|
+
const dynamicModule = _eventbusmodule.EventBusModule.forRoot({
|
|
64
|
+
enabled: true,
|
|
65
|
+
transport: 'memory',
|
|
66
|
+
defaultModuleEvents: {
|
|
67
|
+
enabled: true
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
getBusProvider(dynamicModule.providers).useFactory();
|
|
71
|
+
expect(_eventbusregistry.EventBusRegistry.getModuleConfig('anything')).toEqual({
|
|
72
|
+
enabled: true
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
it('should close the bus and clear the registry on shutdown', async ()=>{
|
|
76
|
+
const dynamicModule = _eventbusmodule.EventBusModule.forRoot({
|
|
77
|
+
enabled: true,
|
|
78
|
+
transport: 'memory'
|
|
79
|
+
});
|
|
80
|
+
const bus = getBusProvider(dynamicModule.providers).useFactory();
|
|
81
|
+
const closeSpy = jest.spyOn(bus, 'close');
|
|
82
|
+
await new _eventbusmodule.EventBusModule().onApplicationShutdown();
|
|
83
|
+
expect(closeSpy).toHaveBeenCalled();
|
|
84
|
+
expect(_eventbusregistry.EventBusRegistry.getBus()).toBeUndefined();
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
describe('when disabled', ()=>{
|
|
88
|
+
it('should still export the bus instance so injecting it keeps working', ()=>{
|
|
89
|
+
const dynamicModule = _eventbusmodule.EventBusModule.forRoot({
|
|
90
|
+
enabled: false
|
|
91
|
+
});
|
|
92
|
+
expect(dynamicModule.exports).toEqual([
|
|
93
|
+
_constants.EVENT_BUS_INSTANCE,
|
|
94
|
+
_constants.EVENT_BUS_MODULE_OPTIONS
|
|
95
|
+
]);
|
|
96
|
+
});
|
|
97
|
+
it('should provide an inert bus', ()=>{
|
|
98
|
+
const dynamicModule = _eventbusmodule.EventBusModule.forRoot({
|
|
99
|
+
enabled: false
|
|
100
|
+
});
|
|
101
|
+
const bus = getBusProvider(dynamicModule.providers).useFactory();
|
|
102
|
+
expect(bus).toBeInstanceOf(_disabledeventbusclass.DisabledEventBus);
|
|
103
|
+
});
|
|
104
|
+
it('should not register the explorer, so no handler is ever bound', ()=>{
|
|
105
|
+
const dynamicModule = _eventbusmodule.EventBusModule.forRoot({
|
|
106
|
+
enabled: false
|
|
107
|
+
});
|
|
108
|
+
expect(dynamicModule.providers).not.toContain(_eventbusexplorer.EventBusExplorer);
|
|
109
|
+
});
|
|
110
|
+
it('should not import DiscoveryModule', ()=>{
|
|
111
|
+
expect(_eventbusmodule.EventBusModule.forRoot({
|
|
112
|
+
enabled: false
|
|
113
|
+
}).imports).toBeUndefined();
|
|
114
|
+
});
|
|
115
|
+
it('should leave the registry empty so every publish becomes a no-op', ()=>{
|
|
116
|
+
const dynamicModule = _eventbusmodule.EventBusModule.forRoot({
|
|
117
|
+
enabled: false
|
|
118
|
+
});
|
|
119
|
+
getBusProvider(dynamicModule.providers).useFactory();
|
|
120
|
+
expect(_eventbusregistry.EventBusRegistry.getBus()).toBeUndefined();
|
|
121
|
+
});
|
|
122
|
+
it('should shut down cleanly with no bus registered', async ()=>{
|
|
123
|
+
_eventbusmodule.EventBusModule.forRoot({
|
|
124
|
+
enabled: false
|
|
125
|
+
});
|
|
126
|
+
await expect(new _eventbusmodule.EventBusModule().onApplicationShutdown()).resolves.toBeUndefined();
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
describe('default from the environment', ()=>{
|
|
130
|
+
it('should be enabled when ENABLE_DOMAIN_EVENTS is true', ()=>{
|
|
131
|
+
process.env.ENABLE_DOMAIN_EVENTS = 'true';
|
|
132
|
+
expect(_eventbusmodule.EventBusModule.forRoot().providers).toContain(_eventbusexplorer.EventBusExplorer);
|
|
133
|
+
});
|
|
134
|
+
it('should be disabled when ENABLE_DOMAIN_EVENTS is false', ()=>{
|
|
135
|
+
process.env.ENABLE_DOMAIN_EVENTS = 'false';
|
|
136
|
+
expect(_eventbusmodule.EventBusModule.forRoot().providers).not.toContain(_eventbusexplorer.EventBusExplorer);
|
|
137
|
+
});
|
|
138
|
+
it('should be disabled when ENABLE_DOMAIN_EVENTS is not set', ()=>{
|
|
139
|
+
delete process.env.ENABLE_DOMAIN_EVENTS;
|
|
140
|
+
expect(_eventbusmodule.EventBusModule.forRoot().providers).not.toContain(_eventbusexplorer.EventBusExplorer);
|
|
141
|
+
});
|
|
142
|
+
it('should let an explicit option override the environment', ()=>{
|
|
143
|
+
process.env.ENABLE_DOMAIN_EVENTS = 'false';
|
|
144
|
+
expect(_eventbusmodule.EventBusModule.forRoot({
|
|
145
|
+
enabled: true
|
|
146
|
+
}).providers).toContain(_eventbusexplorer.EventBusExplorer);
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
});
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", {
|
|
3
|
+
value: true
|
|
4
|
+
});
|
|
5
|
+
function _export(target, all) {
|
|
6
|
+
for(var name in all)Object.defineProperty(target, name, {
|
|
7
|
+
enumerable: true,
|
|
8
|
+
get: Object.getOwnPropertyDescriptor(all, name).get
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
_export(exports, {
|
|
12
|
+
get createModuleEventsProvider () {
|
|
13
|
+
return createModuleEventsProvider;
|
|
14
|
+
},
|
|
15
|
+
get moduleEventsToken () {
|
|
16
|
+
return moduleEventsToken;
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
const _eventbusregistry = require("../../classes/event-bus.registry");
|
|
20
|
+
const moduleEventsToken = (moduleName)=>`${moduleName.toUpperCase().replace(/-/g, '_')}_EVENTS_CONFIG`;
|
|
21
|
+
const providerCache = new Map();
|
|
22
|
+
function createModuleEventsProvider(moduleName, optionsToken) {
|
|
23
|
+
const cacheKey = `${moduleName}|${optionsToken}`;
|
|
24
|
+
const cached = providerCache.get(cacheKey);
|
|
25
|
+
if (cached) return cached;
|
|
26
|
+
const provider = {
|
|
27
|
+
provide: moduleEventsToken(moduleName),
|
|
28
|
+
useFactory: (options)=>{
|
|
29
|
+
const events = options?.config?.events ?? {};
|
|
30
|
+
_eventbusregistry.EventBusRegistry.configureModule(moduleName, events);
|
|
31
|
+
return events;
|
|
32
|
+
},
|
|
33
|
+
inject: [
|
|
34
|
+
optionsToken
|
|
35
|
+
]
|
|
36
|
+
};
|
|
37
|
+
providerCache.set(cacheKey, provider);
|
|
38
|
+
return provider;
|
|
39
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", {
|
|
3
|
+
value: true
|
|
4
|
+
});
|
|
5
|
+
const _eventbusregistry = require("../../classes/event-bus.registry");
|
|
6
|
+
const _moduleeventsprovider = require("./module-events.provider");
|
|
7
|
+
describe('moduleEventsToken', ()=>{
|
|
8
|
+
it('should build an upper snake case token', ()=>{
|
|
9
|
+
expect((0, _moduleeventsprovider.moduleEventsToken)('storage')).toBe('STORAGE_EVENTS_CONFIG');
|
|
10
|
+
expect((0, _moduleeventsprovider.moduleEventsToken)('form-builder')).toBe('FORM_BUILDER_EVENTS_CONFIG');
|
|
11
|
+
});
|
|
12
|
+
});
|
|
13
|
+
describe('createModuleEventsProvider', ()=>{
|
|
14
|
+
beforeEach(()=>{
|
|
15
|
+
_eventbusregistry.EventBusRegistry.reset();
|
|
16
|
+
});
|
|
17
|
+
it('should inject the module options token', ()=>{
|
|
18
|
+
const provider = (0, _moduleeventsprovider.createModuleEventsProvider)('storage', 'STORAGE_MODULE_OPTIONS');
|
|
19
|
+
expect(provider.provide).toBe('STORAGE_EVENTS_CONFIG');
|
|
20
|
+
expect(provider.inject).toEqual([
|
|
21
|
+
'STORAGE_MODULE_OPTIONS'
|
|
22
|
+
]);
|
|
23
|
+
});
|
|
24
|
+
it('should register the events block from the module config', ()=>{
|
|
25
|
+
const provider = (0, _moduleeventsprovider.createModuleEventsProvider)('storage', 'STORAGE_MODULE_OPTIONS');
|
|
26
|
+
const result = provider.useFactory({
|
|
27
|
+
config: {
|
|
28
|
+
events: {
|
|
29
|
+
enabled: true
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
expect(result).toEqual({
|
|
34
|
+
enabled: true
|
|
35
|
+
});
|
|
36
|
+
expect(_eventbusregistry.EventBusRegistry.getModuleConfig('storage')).toEqual({
|
|
37
|
+
enabled: true
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
it('should register an empty config when the module supplies no events block', ()=>{
|
|
41
|
+
const provider = (0, _moduleeventsprovider.createModuleEventsProvider)('auth', 'AUTH_MODULE_OPTIONS');
|
|
42
|
+
provider.useFactory({
|
|
43
|
+
config: {}
|
|
44
|
+
});
|
|
45
|
+
expect(_eventbusregistry.EventBusRegistry.getModuleConfig('auth')).toEqual({});
|
|
46
|
+
});
|
|
47
|
+
it('should return the same provider for repeated calls so module comparisons stay stable', ()=>{
|
|
48
|
+
const first = (0, _moduleeventsprovider.createModuleEventsProvider)('event-manager', 'EVENT_MANAGER_MODULE_OPTIONS');
|
|
49
|
+
const second = (0, _moduleeventsprovider.createModuleEventsProvider)('event-manager', 'EVENT_MANAGER_MODULE_OPTIONS');
|
|
50
|
+
expect(first).toBe(second);
|
|
51
|
+
});
|
|
52
|
+
it('should tolerate missing options', ()=>{
|
|
53
|
+
const provider = (0, _moduleeventsprovider.createModuleEventsProvider)('iam', 'IAM_MODULE_OPTIONS');
|
|
54
|
+
expect(provider.useFactory(undefined)).toEqual({});
|
|
55
|
+
});
|
|
56
|
+
});
|
package/cjs/modules/index.js
CHANGED
|
@@ -4,6 +4,9 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
4
4
|
});
|
|
5
5
|
_export_star(require("./cache/cache.module"), exports);
|
|
6
6
|
_export_star(require("./datasource"), exports);
|
|
7
|
+
_export_star(require("./event-bus/event-bus.explorer"), exports);
|
|
8
|
+
_export_star(require("./event-bus/event-bus.module"), exports);
|
|
9
|
+
_export_star(require("./event-bus/module-events.provider"), exports);
|
|
7
10
|
_export_star(require("./shared-permission-cache/shared-permission-cache.module"), exports);
|
|
8
11
|
_export_star(require("./shared-permission-cache/shared-permission-cache.service"), exports);
|
|
9
12
|
_export_star(require("./utils/utils.module"), exports);
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", {
|
|
3
|
+
value: true
|
|
4
|
+
});
|
|
5
|
+
function _export(target, all) {
|
|
6
|
+
for(var name in all)Object.defineProperty(target, name, {
|
|
7
|
+
enumerable: true,
|
|
8
|
+
get: Object.getOwnPropertyDescriptor(all, name).get
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
_export(exports, {
|
|
12
|
+
get isMultiWildcard () {
|
|
13
|
+
return isMultiWildcard;
|
|
14
|
+
},
|
|
15
|
+
get matchEventPattern () {
|
|
16
|
+
return matchEventPattern;
|
|
17
|
+
},
|
|
18
|
+
get sanitizeEventPayload () {
|
|
19
|
+
return sanitizeEventPayload;
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
const SENSITIVE_KEYS = [
|
|
23
|
+
'password',
|
|
24
|
+
'secret',
|
|
25
|
+
'token',
|
|
26
|
+
'apikey',
|
|
27
|
+
'authorization',
|
|
28
|
+
'salt',
|
|
29
|
+
'hash',
|
|
30
|
+
'credential',
|
|
31
|
+
'accesskey',
|
|
32
|
+
'accountkey',
|
|
33
|
+
'privatekey',
|
|
34
|
+
'connectionstring',
|
|
35
|
+
'passphrase'
|
|
36
|
+
];
|
|
37
|
+
/**
|
|
38
|
+
* Matched whole, not as a substring - `pass` must be redacted without also
|
|
39
|
+
* redacting `bypassCache`, and a storage row's `key` is a path, not a secret.
|
|
40
|
+
*/ const SENSITIVE_EXACT_KEYS = [
|
|
41
|
+
'pass',
|
|
42
|
+
'pwd'
|
|
43
|
+
];
|
|
44
|
+
const REDACTED = '[REDACTED]';
|
|
45
|
+
const CIRCULAR = '[CIRCULAR]';
|
|
46
|
+
const TRUNCATED = '[TRUNCATED]';
|
|
47
|
+
const BINARY = '[BINARY]';
|
|
48
|
+
const MAX_DEPTH = 8;
|
|
49
|
+
function matchEventPattern(pattern, eventName) {
|
|
50
|
+
if (pattern === eventName) return true;
|
|
51
|
+
const patternSegments = pattern.split('.');
|
|
52
|
+
const nameSegments = eventName.split('.');
|
|
53
|
+
return matchSegments(patternSegments, 0, nameSegments, 0);
|
|
54
|
+
}
|
|
55
|
+
function isMultiWildcard(segment) {
|
|
56
|
+
return segment === '#' || segment === '**';
|
|
57
|
+
}
|
|
58
|
+
function matchSegments(pattern, patternIndex, name, nameIndex) {
|
|
59
|
+
if (patternIndex === pattern.length) return nameIndex === name.length;
|
|
60
|
+
const segment = pattern[patternIndex];
|
|
61
|
+
if (isMultiWildcard(segment)) {
|
|
62
|
+
for(let skip = nameIndex; skip <= name.length; skip++){
|
|
63
|
+
if (matchSegments(pattern, patternIndex + 1, name, skip)) return true;
|
|
64
|
+
}
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
if (nameIndex === name.length) return false;
|
|
68
|
+
if (segment !== '*' && segment !== name[nameIndex]) return false;
|
|
69
|
+
return matchSegments(pattern, patternIndex + 1, name, nameIndex + 1);
|
|
70
|
+
}
|
|
71
|
+
function sanitizeEventPayload(value) {
|
|
72
|
+
return sanitizeValue(value, new WeakSet(), 0);
|
|
73
|
+
}
|
|
74
|
+
function sanitizeValue(value, ancestors, depth) {
|
|
75
|
+
if (value === null || typeof value !== 'object') return value;
|
|
76
|
+
if (value instanceof Date) return value;
|
|
77
|
+
if (Buffer.isBuffer(value)) return BINARY;
|
|
78
|
+
if (ancestors.has(value)) return CIRCULAR;
|
|
79
|
+
if (depth >= MAX_DEPTH) return TRUNCATED;
|
|
80
|
+
ancestors.add(value);
|
|
81
|
+
try {
|
|
82
|
+
if (Array.isArray(value)) {
|
|
83
|
+
return value.map((item)=>sanitizeValue(item, ancestors, depth + 1));
|
|
84
|
+
}
|
|
85
|
+
const sanitized = {};
|
|
86
|
+
for (const [key, entry] of Object.entries(value)){
|
|
87
|
+
sanitized[key] = isSensitiveKey(key) ? REDACTED : sanitizeValue(entry, ancestors, depth + 1);
|
|
88
|
+
}
|
|
89
|
+
return sanitized;
|
|
90
|
+
} finally{
|
|
91
|
+
ancestors.delete(value);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
function isSensitiveKey(key) {
|
|
95
|
+
const lowered = key.toLowerCase();
|
|
96
|
+
if (SENSITIVE_EXACT_KEYS.includes(lowered)) return true;
|
|
97
|
+
return SENSITIVE_KEYS.some((sensitive)=>lowered.includes(sensitive));
|
|
98
|
+
}
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", {
|
|
3
|
+
value: true
|
|
4
|
+
});
|
|
5
|
+
const _eventpatternutil = require("./event-pattern.util");
|
|
6
|
+
describe('matchEventPattern', ()=>{
|
|
7
|
+
it('should match an exact event name', ()=>{
|
|
8
|
+
expect((0, _eventpatternutil.matchEventPattern)('storage.folder.created', 'storage.folder.created')).toBe(true);
|
|
9
|
+
});
|
|
10
|
+
it('should not match a different event name', ()=>{
|
|
11
|
+
expect((0, _eventpatternutil.matchEventPattern)('storage.folder.created', 'storage.folder.updated')).toBe(false);
|
|
12
|
+
});
|
|
13
|
+
it('should treat * as exactly one segment', ()=>{
|
|
14
|
+
expect((0, _eventpatternutil.matchEventPattern)('storage.*.created', 'storage.folder.created')).toBe(true);
|
|
15
|
+
expect((0, _eventpatternutil.matchEventPattern)('storage.*', 'storage.folder.created')).toBe(false);
|
|
16
|
+
});
|
|
17
|
+
it('should treat # and ** as any number of segments', ()=>{
|
|
18
|
+
expect((0, _eventpatternutil.matchEventPattern)('storage.#', 'storage.folder.created')).toBe(true);
|
|
19
|
+
expect((0, _eventpatternutil.matchEventPattern)('**', 'auth.appuser.deleted')).toBe(true);
|
|
20
|
+
expect((0, _eventpatternutil.matchEventPattern)('storage.**', 'storage.folder.created')).toBe(true);
|
|
21
|
+
});
|
|
22
|
+
it('should let a multi wildcard match zero segments', ()=>{
|
|
23
|
+
expect((0, _eventpatternutil.matchEventPattern)('storage.folder.created.#', 'storage.folder.created')).toBe(true);
|
|
24
|
+
});
|
|
25
|
+
it('should not match across a different module', ()=>{
|
|
26
|
+
expect((0, _eventpatternutil.matchEventPattern)('storage.#', 'auth.appuser.created')).toBe(false);
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
describe('isMultiWildcard', ()=>{
|
|
30
|
+
it.each([
|
|
31
|
+
[
|
|
32
|
+
'#',
|
|
33
|
+
true
|
|
34
|
+
],
|
|
35
|
+
[
|
|
36
|
+
'**',
|
|
37
|
+
true
|
|
38
|
+
],
|
|
39
|
+
[
|
|
40
|
+
'*',
|
|
41
|
+
false
|
|
42
|
+
],
|
|
43
|
+
[
|
|
44
|
+
'folder',
|
|
45
|
+
false
|
|
46
|
+
]
|
|
47
|
+
])('should classify %s as %s', (segment, expected)=>{
|
|
48
|
+
expect((0, _eventpatternutil.isMultiWildcard)(segment)).toBe(expected);
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
describe('sanitizeEventPayload', ()=>{
|
|
52
|
+
it('should redact credential-like fields', ()=>{
|
|
53
|
+
const result = (0, _eventpatternutil.sanitizeEventPayload)({
|
|
54
|
+
id: '1',
|
|
55
|
+
password: 'secret',
|
|
56
|
+
apiKey: 'k'
|
|
57
|
+
});
|
|
58
|
+
expect(result).toEqual({
|
|
59
|
+
id: '1',
|
|
60
|
+
password: '[REDACTED]',
|
|
61
|
+
apiKey: '[REDACTED]'
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
it('should redact the credential keys used by the storage and email providers', ()=>{
|
|
65
|
+
const result = (0, _eventpatternutil.sanitizeEventPayload)({
|
|
66
|
+
name: 'primary-s3',
|
|
67
|
+
config: {
|
|
68
|
+
region: 'us-east-1',
|
|
69
|
+
bucket: 'assets',
|
|
70
|
+
accessKeyId: 'AKIA...',
|
|
71
|
+
secretAccessKey: 's3cr3t',
|
|
72
|
+
accountKey: 'azure-key',
|
|
73
|
+
privateKey: '-----BEGIN...',
|
|
74
|
+
connectionString: 'DefaultEndpointsProtocol=...;AccountKey=abc',
|
|
75
|
+
auth: {
|
|
76
|
+
user: 'mailer',
|
|
77
|
+
pass: 'hunter2'
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
expect(result).toEqual({
|
|
82
|
+
name: 'primary-s3',
|
|
83
|
+
config: {
|
|
84
|
+
region: 'us-east-1',
|
|
85
|
+
bucket: 'assets',
|
|
86
|
+
accessKeyId: '[REDACTED]',
|
|
87
|
+
secretAccessKey: '[REDACTED]',
|
|
88
|
+
accountKey: '[REDACTED]',
|
|
89
|
+
privateKey: '[REDACTED]',
|
|
90
|
+
connectionString: '[REDACTED]',
|
|
91
|
+
auth: {
|
|
92
|
+
user: 'mailer',
|
|
93
|
+
pass: '[REDACTED]'
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
it('should not over-redact keys that merely contain a credential word', ()=>{
|
|
99
|
+
const result = (0, _eventpatternutil.sanitizeEventPayload)({
|
|
100
|
+
key: 'uploads/2026/report.pdf',
|
|
101
|
+
bypassCache: true,
|
|
102
|
+
passengerCount: 4
|
|
103
|
+
});
|
|
104
|
+
expect(result).toEqual({
|
|
105
|
+
key: 'uploads/2026/report.pdf',
|
|
106
|
+
bypassCache: true,
|
|
107
|
+
passengerCount: 4
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
it('should redact nested fields', ()=>{
|
|
111
|
+
const result = (0, _eventpatternutil.sanitizeEventPayload)({
|
|
112
|
+
user: {
|
|
113
|
+
name: 'Ada',
|
|
114
|
+
passwordHash: 'x'
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
expect(result).toEqual({
|
|
118
|
+
user: {
|
|
119
|
+
name: 'Ada',
|
|
120
|
+
passwordHash: '[REDACTED]'
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
it('should map over arrays', ()=>{
|
|
125
|
+
const result = (0, _eventpatternutil.sanitizeEventPayload)([
|
|
126
|
+
{
|
|
127
|
+
token: 't'
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
token: 'u'
|
|
131
|
+
}
|
|
132
|
+
]);
|
|
133
|
+
expect(result).toEqual([
|
|
134
|
+
{
|
|
135
|
+
token: '[REDACTED]'
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
token: '[REDACTED]'
|
|
139
|
+
}
|
|
140
|
+
]);
|
|
141
|
+
});
|
|
142
|
+
it('should leave dates and primitives untouched', ()=>{
|
|
143
|
+
const date = new Date('2026-01-01T00:00:00.000Z');
|
|
144
|
+
expect((0, _eventpatternutil.sanitizeEventPayload)({
|
|
145
|
+
createdAt: date,
|
|
146
|
+
count: 3
|
|
147
|
+
})).toEqual({
|
|
148
|
+
createdAt: date,
|
|
149
|
+
count: 3
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
it('should mark a circular relation instead of recursing forever', ()=>{
|
|
153
|
+
const folder = {
|
|
154
|
+
id: 'folder-1',
|
|
155
|
+
name: 'Docs'
|
|
156
|
+
};
|
|
157
|
+
const file = {
|
|
158
|
+
id: 'file-1',
|
|
159
|
+
folder
|
|
160
|
+
};
|
|
161
|
+
folder.files = [
|
|
162
|
+
file
|
|
163
|
+
];
|
|
164
|
+
expect((0, _eventpatternutil.sanitizeEventPayload)(folder)).toEqual({
|
|
165
|
+
id: 'folder-1',
|
|
166
|
+
name: 'Docs',
|
|
167
|
+
files: [
|
|
168
|
+
{
|
|
169
|
+
id: 'file-1',
|
|
170
|
+
folder: '[CIRCULAR]'
|
|
171
|
+
}
|
|
172
|
+
]
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
it('should survive a self referencing row', ()=>{
|
|
176
|
+
const row = {
|
|
177
|
+
id: '1'
|
|
178
|
+
};
|
|
179
|
+
row.self = row;
|
|
180
|
+
expect(()=>(0, _eventpatternutil.sanitizeEventPayload)(row)).not.toThrow();
|
|
181
|
+
expect((0, _eventpatternutil.sanitizeEventPayload)(row)).toEqual({
|
|
182
|
+
id: '1',
|
|
183
|
+
self: '[CIRCULAR]'
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
it('should repeat a shared row rather than calling the second one circular', ()=>{
|
|
187
|
+
const shared = {
|
|
188
|
+
id: 'shared'
|
|
189
|
+
};
|
|
190
|
+
expect((0, _eventpatternutil.sanitizeEventPayload)([
|
|
191
|
+
{
|
|
192
|
+
a: shared
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
b: shared
|
|
196
|
+
}
|
|
197
|
+
])).toEqual([
|
|
198
|
+
{
|
|
199
|
+
a: {
|
|
200
|
+
id: 'shared'
|
|
201
|
+
}
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
b: {
|
|
205
|
+
id: 'shared'
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
]);
|
|
209
|
+
});
|
|
210
|
+
it('should truncate a graph deeper than the depth cap', ()=>{
|
|
211
|
+
const deep = {};
|
|
212
|
+
let cursor = deep;
|
|
213
|
+
for(let level = 0; level < 12; level++){
|
|
214
|
+
const next = {};
|
|
215
|
+
cursor.child = next;
|
|
216
|
+
cursor = next;
|
|
217
|
+
}
|
|
218
|
+
let result = (0, _eventpatternutil.sanitizeEventPayload)(deep);
|
|
219
|
+
for(let level = 0; level < 7; level++){
|
|
220
|
+
result = result.child;
|
|
221
|
+
}
|
|
222
|
+
expect(result.child).toBe('[TRUNCATED]');
|
|
223
|
+
});
|
|
224
|
+
it('should replace a buffer with a marker instead of exploding it into indexes', ()=>{
|
|
225
|
+
const result = (0, _eventpatternutil.sanitizeEventPayload)({
|
|
226
|
+
id: '1',
|
|
227
|
+
content: Buffer.from('hello')
|
|
228
|
+
});
|
|
229
|
+
expect(result).toEqual({
|
|
230
|
+
id: '1',
|
|
231
|
+
content: '[BINARY]'
|
|
232
|
+
});
|
|
233
|
+
});
|
|
234
|
+
});
|
package/cjs/utils/index.js
CHANGED
|
@@ -3,7 +3,9 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
3
3
|
value: true
|
|
4
4
|
});
|
|
5
5
|
_export_star(require("./date-time.util"), exports);
|
|
6
|
+
_export_star(require("./event-pattern.util"), exports);
|
|
6
7
|
_export_star(require("./html-sanitizer.util"), exports);
|
|
8
|
+
_export_star(require("./optional-module.util"), exports);
|
|
7
9
|
_export_star(require("./permission-cache-key.util"), exports);
|
|
8
10
|
_export_star(require("./query-helpers.util"), exports);
|
|
9
11
|
_export_star(require("./request.util"), exports);
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Loads an optional peer dependency at runtime.
|
|
3
|
+
*
|
|
4
|
+
* The specifier has to stay invisible to the bundler. A plain `import(name)`
|
|
5
|
+
* looks dynamic in source but webpack rewrites it into a context module rooted
|
|
6
|
+
* at this directory - one that can only ever resolve files sitting next to this
|
|
7
|
+
* one - so `amqplib` and `kafkajs` fail to load in the bundled app no matter how
|
|
8
|
+
* correctly they are installed. Both lookups below are assembled at runtime,
|
|
9
|
+
* where no bundler can rewrite them.
|
|
10
|
+
*
|
|
11
|
+
* `require` is used first because it is what the CommonJS builds actually run
|
|
12
|
+
* on; native ESM has no `require`, and there `import()` is the only way in.
|
|
13
|
+
*/ "use strict";
|
|
14
|
+
Object.defineProperty(exports, "__esModule", {
|
|
15
|
+
value: true
|
|
16
|
+
});
|
|
17
|
+
Object.defineProperty(exports, "loadOptionalModule", {
|
|
18
|
+
enumerable: true,
|
|
19
|
+
get: function() {
|
|
20
|
+
return loadOptionalModule;
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
async function loadOptionalModule(name) {
|
|
24
|
+
const runtimeRequire = resolveRuntimeRequire();
|
|
25
|
+
if (runtimeRequire) return runtimeRequire(name);
|
|
26
|
+
const runtimeImport = new Function('specifier', 'return import(specifier);');
|
|
27
|
+
return runtimeImport(name);
|
|
28
|
+
}
|
|
29
|
+
/** Undefined under native ESM and inside a bundle that shadows nothing back */ function resolveRuntimeRequire() {
|
|
30
|
+
try {
|
|
31
|
+
return eval('typeof require === "function" ? require : undefined');
|
|
32
|
+
} catch {
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", {
|
|
3
|
+
value: true
|
|
4
|
+
});
|
|
5
|
+
const _optionalmoduleutil = require("./optional-module.util");
|
|
6
|
+
describe('loadOptionalModule', ()=>{
|
|
7
|
+
it('should resolve an installed module', async ()=>{
|
|
8
|
+
const events = await (0, _optionalmoduleutil.loadOptionalModule)('events');
|
|
9
|
+
expect(typeof events.EventEmitter).toBe('function');
|
|
10
|
+
});
|
|
11
|
+
it('should reject for a module that is not installed', async ()=>{
|
|
12
|
+
await expect((0, _optionalmoduleutil.loadOptionalModule)('definitely-not-installed-pkg')).rejects.toThrow();
|
|
13
|
+
});
|
|
14
|
+
});
|
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
import { DataSource, EntityTarget, QueryRunner, Repository, SelectQueryBuilder } from 'typeorm';
|
|
2
2
|
import { DeleteDto, FilterAndPaginationDto } from '../dtos';
|
|
3
3
|
import { Identity, RawEntity } from '../entities';
|
|
4
|
-
import {
|
|
4
|
+
import { EventAction } from '../enums/event-action.enum';
|
|
5
|
+
import { DomainEventActionName, IDataSourceProvider, ILoggedUserInfo, IService } from '../interfaces';
|
|
5
6
|
import { UtilsService } from '../modules/utils/utils.service';
|
|
7
|
+
import { IPublishDomainEventInput } from './domain-event.publisher';
|
|
6
8
|
import { HybridCache } from './hybrid-cache.class';
|
|
9
|
+
interface IEventGroup<T> {
|
|
10
|
+
action: EventAction;
|
|
11
|
+
entities: T[];
|
|
12
|
+
idsOnly?: boolean;
|
|
13
|
+
}
|
|
7
14
|
export declare abstract class ApiService<CreateDtoT extends object, UpdateDtoT extends {
|
|
8
15
|
id: string;
|
|
9
16
|
}, InterfaceT extends Identity, EntityT extends Identity> implements IService<CreateDtoT, UpdateDtoT, InterfaceT> {
|
|
@@ -35,6 +42,8 @@ export declare abstract class ApiService<CreateDtoT extends object, UpdateDtoT e
|
|
|
35
42
|
total: number;
|
|
36
43
|
}>;
|
|
37
44
|
delete(option: DeleteDto, user: ILoggedUserInfo | null): Promise<null>;
|
|
45
|
+
protected publishLifecycleEvents(groups: IEventGroup<EntityT>[], user: ILoggedUserInfo | null): Promise<void>;
|
|
46
|
+
protected publishDomainAction(action: DomainEventActionName, input?: Omit<IPublishDomainEventInput, 'module' | 'entity' | 'action'>): Promise<void>;
|
|
38
47
|
clearCacheForAll(): Promise<void>;
|
|
39
48
|
clearCacheForId(entities: EntityT[]): Promise<void>;
|
|
40
49
|
private handleError;
|
|
@@ -75,3 +84,4 @@ export declare abstract class ApiService<CreateDtoT extends object, UpdateDtoT e
|
|
|
75
84
|
protected convertEntityListToResponseListDto(entities: EntityT[] | RawEntity[], _isRaw: boolean): InterfaceT[];
|
|
76
85
|
protected getEntityClass(): Promise<EntityT>;
|
|
77
86
|
}
|
|
87
|
+
export {};
|