@squiz/optimization-utils 2.0.2 → 3.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.
Files changed (72) hide show
  1. package/dist/array/chunkify.d.ts +1 -0
  2. package/dist/array/chunkify.js +12 -0
  3. package/dist/array/chunkify.js.map +1 -0
  4. package/dist/array/uniqueWith.d.ts +1 -0
  5. package/dist/array/uniqueWith.js +6 -0
  6. package/dist/array/uniqueWith.js.map +1 -0
  7. package/dist/index.d.ts +3 -13
  8. package/dist/index.js +3 -13
  9. package/dist/index.js.map +1 -1
  10. package/dist/{typesUtils → types}/utilities.js.map +1 -1
  11. package/package.json +3 -16
  12. package/CHANGELOG.md +0 -43
  13. package/dist/cloudflare/CloudflareKVHttpService.d.ts +0 -11
  14. package/dist/cloudflare/CloudflareKVHttpService.js +0 -19
  15. package/dist/cloudflare/CloudflareKVHttpService.js.map +0 -1
  16. package/dist/cloudflare/ImplCloudflareKVHttpService.d.ts +0 -22
  17. package/dist/cloudflare/ImplCloudflareKVHttpService.js +0 -94
  18. package/dist/cloudflare/ImplCloudflareKVHttpService.js.map +0 -1
  19. package/dist/config/ConfigurationLoader.d.ts +0 -23
  20. package/dist/config/ConfigurationLoader.js +0 -98
  21. package/dist/config/ConfigurationLoader.js.map +0 -1
  22. package/dist/date/DateManipulator.d.ts +0 -6
  23. package/dist/date/DateManipulator.js +0 -21
  24. package/dist/date/DateManipulator.js.map +0 -1
  25. package/dist/event/AggregateRoot.d.ts +0 -4
  26. package/dist/event/AggregateRoot.js +0 -3
  27. package/dist/event/AggregateRoot.js.map +0 -1
  28. package/dist/event/DomainEvent.d.ts +0 -14
  29. package/dist/event/DomainEvent.js +0 -28
  30. package/dist/event/DomainEvent.js.map +0 -1
  31. package/dist/event/DynamoDBEventMapper.d.ts +0 -39
  32. package/dist/event/DynamoDBEventMapper.js +0 -58
  33. package/dist/event/DynamoDBEventMapper.js.map +0 -1
  34. package/dist/event/EventHandler.d.ts +0 -14
  35. package/dist/event/EventHandler.js +0 -43
  36. package/dist/event/EventHandler.js.map +0 -1
  37. package/dist/scheduler/EventBridgeScheduler.d.ts +0 -21
  38. package/dist/scheduler/EventBridgeScheduler.js +0 -130
  39. package/dist/scheduler/EventBridgeScheduler.js.map +0 -1
  40. package/dist/scheduler/Scheduler.d.ts +0 -21
  41. package/dist/scheduler/Scheduler.js +0 -17
  42. package/dist/scheduler/Scheduler.js.map +0 -1
  43. package/dist/testing/mock.d.ts +0 -4
  44. package/dist/testing/mock.js +0 -17
  45. package/dist/testing/mock.js.map +0 -1
  46. package/dist/typesUtils/DynamoDB.d.ts +0 -4
  47. package/dist/typesUtils/DynamoDB.js +0 -3
  48. package/dist/typesUtils/DynamoDB.js.map +0 -1
  49. package/src/cloudflare/CloudflareKVHttpService.ts +0 -20
  50. package/src/cloudflare/ImplCloudflareKVHttpService.ts +0 -129
  51. package/src/cloudflare/__tests__/ImplCloudflareKVHttpService.spec.ts +0 -178
  52. package/src/config/ConfigurationLoader.ts +0 -74
  53. package/src/config/__tests__/ConfigurationLoader.spec.ts +0 -62
  54. package/src/date/DateManipulator.ts +0 -29
  55. package/src/date/__tests__/DateManipulator.spec.ts +0 -64
  56. package/src/event/AggregateRoot.ts +0 -5
  57. package/src/event/DomainEvent.ts +0 -53
  58. package/src/event/DynamoDBEventMapper.ts +0 -75
  59. package/src/event/EventHandler.ts +0 -57
  60. package/src/event/__tests__/DynamoDBEventMapper.spec.ts +0 -121
  61. package/src/index.ts +0 -21
  62. package/src/object/__tests__/getProperty.spec.ts +0 -17
  63. package/src/object/getProperty.ts +0 -21
  64. package/src/scheduler/EventBridgeScheduler.ts +0 -172
  65. package/src/scheduler/Scheduler.ts +0 -32
  66. package/src/scheduler/__tests__/EventBridgeScheduler.spec.ts +0 -308
  67. package/src/testing/mock.ts +0 -15
  68. package/src/typesUtils/DynamoDB.ts +0 -17
  69. package/src/typesUtils/utilities.ts +0 -11
  70. package/tsconfig.json +0 -13
  71. /package/dist/{typesUtils → types}/utilities.d.ts +0 -0
  72. /package/dist/{typesUtils → types}/utilities.js +0 -0
@@ -1,178 +0,0 @@
1
- import {
2
- CloudflareConfig,
3
- ImplCloudflareKVHttpService,
4
- } from '../ImplCloudflareKVHttpService';
5
- import { Container } from 'inversify';
6
- import {
7
- createHttpClientMock,
8
- HttpRequestBuilderFactory,
9
- HttpClient,
10
- HttpMethod,
11
- HttpRequestOptions,
12
- HttpResponse,
13
- } from '@squiz/optimization-http-client';
14
- import { Logger, createLoggerMock } from '@squiz/optimization-logger';
15
- import { faker } from '@faker-js/faker';
16
- import { z } from 'zod';
17
-
18
- describe('CloudflareKVHttpService', () => {
19
- const EXAMPLE_SCHEMA = z.object({
20
- example: z.string(),
21
- });
22
-
23
- type ExampleSchemaType = z.infer<typeof EXAMPLE_SCHEMA>;
24
-
25
- let service: ImplCloudflareKVHttpService<ExampleSchemaType>;
26
- let httpClient: HttpClient;
27
- const config: CloudflareConfig = {
28
- domain: 'test-domain.com',
29
- accountId: 'test-account-id',
30
- namespace: 'test-namespace-id',
31
- apiKey: 'test-api-key',
32
- };
33
- const expectedHeaders = {
34
- 'Content-Type': 'application/json',
35
- Authorization: `Bearer ${config.apiKey}`,
36
- };
37
-
38
- beforeEach(() => {
39
- const container = new Container({ defaultScope: 'Singleton' });
40
-
41
- container
42
- .bind(ImplCloudflareKVHttpService)
43
- .toDynamicValue(({ container: c }) => {
44
- return new ImplCloudflareKVHttpService(
45
- () => Promise.resolve(config),
46
- c.get(HttpRequestBuilderFactory),
47
- c.get(Logger),
48
- EXAMPLE_SCHEMA,
49
- );
50
- });
51
- container.bind(HttpRequestBuilderFactory).toSelf();
52
- container.bind(HttpClient).toConstantValue(createHttpClientMock());
53
- container.bind(Logger).toConstantValue(createLoggerMock());
54
-
55
- service = container.get(ImplCloudflareKVHttpService);
56
- httpClient = container.get(HttpClient);
57
- });
58
-
59
- describe('getValues', () => {
60
- const key = 'example-key';
61
-
62
- it('should get values from Cloudflare KV', async () => {
63
- const expectedResponse = { example: faker.string.uuid() };
64
-
65
- const expectedURL =
66
- 'https://test-domain.com/client/v4/accounts' +
67
- '/test-account-id/storage/kv/namespaces/test-namespace-id' +
68
- `/values/${key}`;
69
-
70
- jest.spyOn(httpClient, 'sendRequest').mockResolvedValueOnce(
71
- new HttpResponse({
72
- statusCode: 200,
73
- body: JSON.stringify(expectedResponse),
74
- }),
75
- );
76
-
77
- const result = await service.getValues(key);
78
-
79
- expect(result).toEqual(expectedResponse);
80
- expect(httpClient.sendRequest).toHaveBeenCalledWith({
81
- url: expectedURL,
82
- method: HttpMethod.GET,
83
- headers: expectedHeaders,
84
- handleHttpErrorStatusCodes: [404],
85
- } as HttpRequestOptions);
86
- });
87
-
88
- it('should throw an error if fetch fails', async () => {
89
- jest.spyOn(httpClient, 'sendRequest').mockRejectedValueOnce(new Error());
90
-
91
- await expect(service.getValues(key)).rejects.toThrow();
92
- });
93
- });
94
-
95
- describe('putBulk', () => {
96
- it('should call the put bulk endpoint', async () => {
97
- const value: ExampleSchemaType = { example: faker.string.uuid() };
98
- const expectedURL =
99
- 'https://test-domain.com/client/v4/accounts' +
100
- '/test-account-id/storage/kv/namespaces/test-namespace-id' +
101
- `/bulk`;
102
-
103
- jest
104
- .spyOn(httpClient, 'sendRequest')
105
- .mockResolvedValueOnce(
106
- new HttpResponse({ statusCode: 200, body: undefined }),
107
- );
108
-
109
- await service.putBulk([
110
- {
111
- key: 'someKey',
112
- value: value,
113
- },
114
- ]);
115
-
116
- expect(httpClient.sendRequest).toHaveBeenCalledWith({
117
- url: expectedURL,
118
- method: HttpMethod.PUT,
119
- headers: expectedHeaders,
120
- body: [
121
- {
122
- key: 'someKey',
123
- value: JSON.stringify(value),
124
- },
125
- ],
126
- } as HttpRequestOptions);
127
- });
128
-
129
- it('should not call the put bulk endpoint if the batch data array is empty', async () => {
130
- await service.putBulk([]);
131
-
132
- expect(httpClient.sendRequest).toHaveBeenCalledTimes(0);
133
- });
134
-
135
- it('should throw exception if the items amount exceeds 10_000', async () => {
136
- const batch = Array(10_001)
137
- .fill('')
138
- .map(() => ({
139
- key: faker.string.uuid(),
140
- value: { example: faker.string.uuid() },
141
- }));
142
-
143
- await expect(() => service.putBulk(batch)).rejects.toThrow(
144
- 'The max items count has been exceeded. The max items count is 10000',
145
- );
146
- });
147
- });
148
-
149
- describe('deleteBulk', () => {
150
- it('should call the delete bulk endpoint', async () => {
151
- const expectedURL =
152
- 'https://test-domain.com/client/v4/accounts' +
153
- '/test-account-id/storage/kv/namespaces/test-namespace-id' +
154
- `/bulk`;
155
-
156
- jest
157
- .spyOn(httpClient, 'sendRequest')
158
- .mockResolvedValueOnce(
159
- new HttpResponse({ statusCode: 200, body: undefined }),
160
- );
161
-
162
- await service.deleteBulk(['someKey']);
163
-
164
- expect(httpClient.sendRequest).toHaveBeenCalledWith({
165
- url: expectedURL,
166
- method: HttpMethod.DELETE,
167
- headers: expectedHeaders,
168
- body: ['someKey'],
169
- } as HttpRequestOptions);
170
- });
171
- });
172
-
173
- it('should not call the delete bulk endpoint if passed empty keys array', async () => {
174
- await service.deleteBulk([]);
175
-
176
- expect(httpClient.sendRequest).toHaveBeenCalledTimes(0);
177
- });
178
- });
@@ -1,74 +0,0 @@
1
- import { config } from 'dotenv';
2
- import * as process from 'process';
3
- import * as path from 'path';
4
- import { injectable } from 'inversify';
5
- import { z, ZodType } from 'zod';
6
- import {
7
- HttpMethod,
8
- HttpRequestBuilderFactory,
9
- } from '@squiz/optimization-http-client';
10
-
11
- export abstract class ConfigurationLoader<TSchema extends ZodType> {
12
- abstract load(): Promise<z.infer<TSchema>>;
13
- }
14
-
15
- @injectable()
16
- export class DotEnvConfigurationLoader<TSchema extends ZodType>
17
- implements ConfigurationLoader<TSchema>
18
- {
19
- constructor(private readonly schema: TSchema) {}
20
-
21
- async load(): Promise<z.infer<TSchema>> {
22
- const output = config({
23
- path: path.join(__dirname, '../../../../env/.env'),
24
- });
25
-
26
- if (output.error) {
27
- throw output.error;
28
- }
29
-
30
- return this.schema.parse(process.env);
31
- }
32
- }
33
-
34
- export type LambdaLayerAppConfigConfigurationLoaderConfig = {
35
- appConfigName: string;
36
- env: string;
37
- configurationName: string;
38
- };
39
-
40
- @injectable()
41
- export class LambdaLayerAppConfigConfigurationLoader<TSchema extends ZodType>
42
- implements ConfigurationLoader<TSchema>
43
- {
44
- constructor(
45
- private readonly opts: LambdaLayerAppConfigConfigurationLoaderConfig,
46
- private readonly httpRequestBuilderFactory: HttpRequestBuilderFactory,
47
- private readonly schema: TSchema,
48
- ) {}
49
-
50
- async load(): Promise<z.infer<TSchema>> {
51
- const configuration = await this.fetchFromApi();
52
-
53
- return this.schema.parse(configuration);
54
- }
55
-
56
- private async fetchFromApi(): Promise<unknown> {
57
- const application = this.opts.appConfigName;
58
- const environment = this.opts.env;
59
- const configuration = this.opts.configurationName;
60
- // the http://localhost:2772 represents the URL to the AWS AppConfig Lambda layer
61
- // the documentation: https://docs.aws.amazon.com/appconfig/latest/userguide/appconfig-integration-lambda-extensions.html
62
- const url =
63
- `http://localhost:2772` +
64
- `/applications/${application}/environments/${environment}/configurations/${configuration}`;
65
-
66
- const response = await this.httpRequestBuilderFactory
67
- .create()
68
- .url(url)
69
- .method(HttpMethod.GET)
70
- .sendRequest();
71
-
72
- return response.body;
73
- }
74
- }
@@ -1,62 +0,0 @@
1
- import {
2
- ConfigurationLoader,
3
- LambdaLayerAppConfigConfigurationLoader,
4
- LambdaLayerAppConfigConfigurationLoaderConfig,
5
- } from '../ConfigurationLoader';
6
- import {
7
- HttpRequestBuilderFactory,
8
- HttpClient,
9
- HttpMethod,
10
- HttpResponse,
11
- createHttpClientMock,
12
- } from '@squiz/optimization-http-client';
13
- import { faker } from '@faker-js/faker';
14
- import { z } from 'zod';
15
-
16
- describe('LambdaLayerAppConfigConfigurationLoader', () => {
17
- const opts: LambdaLayerAppConfigConfigurationLoaderConfig = {
18
- env: 'local',
19
- appConfigName: 'example-app',
20
- configurationName: 'main',
21
- };
22
- const CONFIG_SCHEMA = z.object({
23
- EXAMPLE_VALUE: z.string(),
24
- });
25
-
26
- type Config = z.infer<typeof CONFIG_SCHEMA>;
27
-
28
- let configurationLoader: ConfigurationLoader<typeof CONFIG_SCHEMA>;
29
- let httpClient: HttpClient;
30
-
31
- beforeEach(() => {
32
- httpClient = createHttpClientMock();
33
- configurationLoader = new LambdaLayerAppConfigConfigurationLoader<
34
- typeof CONFIG_SCHEMA
35
- >(opts, new HttpRequestBuilderFactory(httpClient), CONFIG_SCHEMA);
36
- });
37
-
38
- it('should call AWS AppConfig Layer with passed arguments', async () => {
39
- const expectedResult: Config = {
40
- EXAMPLE_VALUE: faker.word.words(),
41
- };
42
-
43
- jest.spyOn(httpClient, 'sendRequest').mockResolvedValueOnce(
44
- new HttpResponse({
45
- body: expectedResult,
46
- statusCode: 200,
47
- }),
48
- );
49
-
50
- const result = await configurationLoader.load();
51
-
52
- const expectedURL =
53
- `http://localhost:2772` +
54
- `/applications/example-app/environments/local/configurations/main`;
55
-
56
- expect(result).toEqual(expectedResult);
57
- expect(httpClient.sendRequest).toHaveBeenCalledWith({
58
- url: expectedURL,
59
- method: HttpMethod.GET,
60
- });
61
- });
62
- });
@@ -1,29 +0,0 @@
1
- export type DateManipulator = {
2
- addDays(days: number): DateManipulator;
3
- addMonths(months: number): DateManipulator;
4
- valueOf(): Date;
5
- };
6
-
7
- export function DateManipulator(input: Date): DateManipulator {
8
- const addDays = (days: number): DateManipulator => {
9
- const copiedDate = new Date(input);
10
-
11
- copiedDate.setDate(copiedDate.getDate() + days);
12
-
13
- return DateManipulator(copiedDate);
14
- };
15
-
16
- const addMonths = (months: number): DateManipulator => {
17
- const copiedDate = new Date(input);
18
-
19
- copiedDate.setMonth(copiedDate.getMonth() + months);
20
-
21
- return DateManipulator(copiedDate);
22
- };
23
-
24
- return {
25
- addDays,
26
- addMonths,
27
- valueOf: () => new Date(input),
28
- };
29
- }
@@ -1,64 +0,0 @@
1
- import { DateManipulator } from '../DateManipulator';
2
-
3
- describe('DateManipulator', () => {
4
- describe('addDays', () => {
5
- it('should add given days to date', () => {
6
- const result = DateManipulator(new Date('2020-01-01T00:00:00.000Z'))
7
- .addDays(32)
8
- .valueOf();
9
-
10
- expect(result).toEqual(new Date('2020-02-02T00:00:00.000Z'));
11
- });
12
-
13
- it('should not change the date if passed 0 days', () => {
14
- const result = DateManipulator(new Date('2020-01-01T00:00:00.000Z'))
15
- .addDays(0)
16
- .valueOf();
17
-
18
- expect(result).toEqual(new Date('2020-01-01T00:00:00.000Z'));
19
- });
20
-
21
- it('should not mutate the input date', () => {
22
- const originalDate = new Date('2020-01-01T00:00:00.000Z');
23
-
24
- const result = DateManipulator(originalDate).addDays(1).valueOf();
25
-
26
- expect(result).not.toEqual(originalDate);
27
- });
28
- });
29
-
30
- describe('addMonths', () => {
31
- it('should add given days to date', () => {
32
- const result = DateManipulator(new Date('2020-01-01T00:00:00.000Z'))
33
- .addMonths(12)
34
- .valueOf();
35
-
36
- expect(result).toEqual(new Date('2021-01-01T00:00:00.000Z'));
37
- });
38
-
39
- it('should not change the date if passed 0 months', () => {
40
- const result = DateManipulator(new Date('2020-01-01T00:00:00.000Z'))
41
- .addMonths(0)
42
- .valueOf();
43
-
44
- expect(result).toEqual(new Date('2020-01-01T00:00:00.000Z'));
45
- });
46
-
47
- it('should not mutate the input date', () => {
48
- const originalDate = new Date('2020-01-01T00:00:00.000Z');
49
-
50
- const result = DateManipulator(originalDate).addMonths(1).valueOf();
51
-
52
- expect(result).not.toEqual(originalDate);
53
- });
54
- });
55
-
56
- it('should return combined result', () => {
57
- const result = DateManipulator(new Date('2020-01-01T00:00:00.000Z'))
58
- .addMonths(12)
59
- .addDays(1)
60
- .valueOf();
61
-
62
- expect(result).toEqual(new Date('2021-01-02T00:00:00.000Z'));
63
- });
64
- });
@@ -1,5 +0,0 @@
1
- import { DomainEvent } from './DomainEvent';
2
-
3
- export interface AggregateRoot {
4
- events(): Array<DomainEvent>;
5
- }
@@ -1,53 +0,0 @@
1
- import 'reflect-metadata';
2
-
3
- export interface DomainEvent<
4
- TData extends Record<string, unknown> = Record<string, unknown>,
5
- > {
6
- readonly eventId: string;
7
- readonly name: string;
8
- readonly detail: TData;
9
- readonly time: Date;
10
- readonly version: string;
11
- readonly entityId: string;
12
- }
13
-
14
- const DOMAIN_EVENT_SYMBOL = Symbol();
15
-
16
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
17
- export type DomainEventConstructor = new (...any: Array<any>) => DomainEvent;
18
- export type DomainEventName = string;
19
-
20
- export function getDomainEventType(
21
- name: DomainEventName,
22
- ): DomainEventConstructor | undefined {
23
- const domainEvents = getDomainMetadata();
24
-
25
- return domainEvents.find((d) => d.name === name);
26
- }
27
-
28
- export function getDomainMetadata(): Array<DomainEventConstructor> {
29
- return Reflect.getMetadata(DOMAIN_EVENT_SYMBOL, Object) ?? [];
30
- }
31
-
32
- function setDomainMetadata(domainEvent: DomainEventConstructor): void {
33
- const metadata = getDomainMetadata();
34
- const isDomainEventExist = getDomainEventType(domainEvent.name);
35
-
36
- if (isDomainEventExist) {
37
- throw new Error(
38
- `The DomainEvent Name has been used two times: "${domainEvent.name}". Each event name should be uniq`,
39
- );
40
- }
41
-
42
- Reflect.defineMetadata(
43
- DOMAIN_EVENT_SYMBOL,
44
- [...metadata, domainEvent],
45
- Object,
46
- );
47
- }
48
-
49
- export function AsDomainEvent(): ClassDecorator {
50
- return (target) => {
51
- setDomainMetadata(target as unknown as DomainEventConstructor);
52
- };
53
- }
@@ -1,75 +0,0 @@
1
- import { DomainEvent, getDomainEventType } from './DomainEvent';
2
- import { z } from 'zod';
3
- import { MarshalledResult } from '../typesUtils/DynamoDB';
4
- import { marshall, unmarshall } from '@aws-sdk/util-dynamodb';
5
- import { AttributeValue } from '@aws-sdk/client-dynamodb';
6
- import { DateManipulator } from '../date/DateManipulator';
7
-
8
- const notEmptyString = z.string().min(1);
9
- const EVENT_DYNAMO_DB_MODEL_SCHEMA = z.object({
10
- eventId: notEmptyString,
11
- name: notEmptyString,
12
- detail: z.object({}).catchall(z.any()),
13
- time: z.string().datetime(),
14
- version: notEmptyString,
15
- ttl: z.number(),
16
- entityId: z.string(),
17
- });
18
-
19
- export type EventDynamoDBModel = z.infer<typeof EVENT_DYNAMO_DB_MODEL_SCHEMA>;
20
- type EventDynamoMarshalledModel = MarshalledResult<EventDynamoDBModel>;
21
-
22
- export const eventToDynamoDB = (
23
- event: DomainEvent,
24
- ): EventDynamoMarshalledModel => {
25
- const timestampPlus30Days = DateManipulator(event.time)
26
- .addDays(30)
27
- .valueOf()
28
- .getTime();
29
-
30
- const dynamoDBModel: EventDynamoDBModel = {
31
- eventId: event.eventId,
32
- name: event.name,
33
- detail: JSON.parse(JSON.stringify(event.detail)),
34
- time: event.time.toISOString(),
35
- version: event.version,
36
- ttl: timestampPlus30Days / 1000,
37
- entityId: event.entityId,
38
- };
39
-
40
- return marshall(dynamoDBModel, {
41
- removeUndefinedValues: true,
42
- }) as EventDynamoMarshalledModel;
43
- };
44
-
45
- export const toEventFromDynamoDb = <TData extends DomainEvent>(
46
- dynamoItem: Record<string, AttributeValue>,
47
- ): TData => {
48
- const unmarshalled = unmarshall(dynamoItem);
49
-
50
- return toEventFromPlain(unmarshalled);
51
- };
52
-
53
- export const toEventFromPlain = <TData extends DomainEvent>(
54
- plainObject: Record<string, unknown>,
55
- ): TData => {
56
- const dynamoDBModel = EVENT_DYNAMO_DB_MODEL_SCHEMA.omit({ ttl: true }).parse(
57
- plainObject,
58
- );
59
- const eventType = getDomainEventType(dynamoDBModel.name);
60
-
61
- if (!eventType) {
62
- throw new Error(
63
- `Cannot map the event "${dynamoDBModel.name}" because does not exist`,
64
- );
65
- }
66
-
67
- return Object.assign(new eventType({}), {
68
- eventId: dynamoDBModel.eventId,
69
- detail: dynamoDBModel.detail,
70
- time: new Date(dynamoDBModel.time),
71
- version: dynamoDBModel.version,
72
- name: dynamoDBModel.name,
73
- entityId: dynamoDBModel.entityId,
74
- }) as TData;
75
- };
@@ -1,57 +0,0 @@
1
- import { injectable } from 'inversify';
2
- import { DomainEvent } from './DomainEvent';
3
-
4
- @injectable()
5
- export abstract class EventHandler<TEvent extends DomainEvent = DomainEvent> {
6
- abstract execute(event: TEvent): Promise<void>;
7
- }
8
-
9
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
10
- type Constructor = new (...args: Array<any>) => any;
11
- type EventConstructor = Constructor;
12
- type EventHandlerConstructor = Constructor;
13
- type EventHandlerMetadata = Array<{
14
- eventHandler: EventHandlerConstructor;
15
- event: EventConstructor;
16
- }>;
17
-
18
- const EVENT_HANDLER_SYMBOL = Symbol();
19
-
20
- export function getEventHandlerMetadata(): EventHandlerMetadata {
21
- return Reflect.getMetadata(EVENT_HANDLER_SYMBOL, Object) ?? [];
22
- }
23
-
24
- function setEventHandlerMetadata({
25
- event,
26
- eventHandler,
27
- }: {
28
- eventHandler: EventHandlerConstructor;
29
- event: EventConstructor;
30
- }): void {
31
- const metadata = getEventHandlerMetadata();
32
- const handlerExists = metadata.find(
33
- (m) => m.eventHandler.name === eventHandler.name,
34
- );
35
-
36
- if (handlerExists) {
37
- throw new Error(
38
- `The EventHandler has been used two times: ${eventHandler.name}. The EventHandler has to have uniq name`,
39
- );
40
- }
41
-
42
- const newMetadata: EventHandlerMetadata = [
43
- ...getEventHandlerMetadata(),
44
- { eventHandler, event },
45
- ];
46
-
47
- Reflect.defineMetadata(EVENT_HANDLER_SYMBOL, newMetadata, Object);
48
- }
49
-
50
- export function AsEventHandler(event: EventConstructor): ClassDecorator {
51
- return (target) => {
52
- setEventHandlerMetadata({
53
- event,
54
- eventHandler: target as unknown as EventHandlerConstructor,
55
- });
56
- };
57
- }