@squiz/optimization-utils 2.0.1 → 2.0.3
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/dist/cloudflare/ImplCloudflareKVHttpService.d.ts +2 -2
- package/dist/cloudflare/ImplCloudflareKVHttpService.js +8 -10
- package/dist/cloudflare/ImplCloudflareKVHttpService.js.map +1 -1
- package/dist/config/ConfigurationLoader.d.ts +1 -1
- package/dist/config/ConfigurationLoader.js +3 -4
- package/dist/config/ConfigurationLoader.js.map +1 -1
- package/dist/index.d.ts +0 -11
- package/dist/index.js +0 -11
- package/dist/index.js.map +1 -1
- package/dist/scheduler/EventBridgeScheduler.d.ts +1 -1
- package/dist/scheduler/EventBridgeScheduler.js +4 -5
- package/dist/scheduler/EventBridgeScheduler.js.map +1 -1
- package/dist/testing/mock.d.ts +0 -8
- package/dist/testing/mock.js +1 -35
- package/dist/testing/mock.js.map +1 -1
- package/package.json +4 -1
- package/CHANGELOG.md +0 -37
- package/dist/exception/DomainException.d.ts +0 -18
- package/dist/exception/DomainException.js +0 -41
- package/dist/exception/DomainException.js.map +0 -1
- package/dist/httpClient/FetchHttpClient.d.ts +0 -7
- package/dist/httpClient/FetchHttpClient.js +0 -86
- package/dist/httpClient/FetchHttpClient.js.map +0 -1
- package/dist/httpClient/HttpClient.d.ts +0 -25
- package/dist/httpClient/HttpClient.js +0 -45
- package/dist/httpClient/HttpClient.js.map +0 -1
- package/dist/httpClient/HttpRequestBuilder.d.ts +0 -22
- package/dist/httpClient/HttpRequestBuilder.js +0 -126
- package/dist/httpClient/HttpRequestBuilder.js.map +0 -1
- package/dist/logger/Logger.d.ts +0 -10
- package/dist/logger/Logger.js +0 -30
- package/dist/logger/Logger.js.map +0 -1
- package/dist/logger/LoggerMessage.d.ts +0 -43
- package/dist/logger/LoggerMessage.js +0 -111
- package/dist/logger/LoggerMessage.js.map +0 -1
- package/dist/logger/LogsHandler.d.ts +0 -11
- package/dist/logger/LogsHandler.js +0 -66
- package/dist/logger/LogsHandler.js.map +0 -1
- package/dist/logger/RemoteLogger.d.ts +0 -30
- package/dist/logger/RemoteLogger.js +0 -35
- package/dist/logger/RemoteLogger.js.map +0 -1
- package/dist/logger/SquizRemoteLogger.d.ts +0 -53
- package/dist/logger/SquizRemoteLogger.js +0 -128
- package/dist/logger/SquizRemoteLogger.js.map +0 -1
- package/dist/validation/handleValidation.d.ts +0 -2
- package/dist/validation/handleValidation.js +0 -11
- package/dist/validation/handleValidation.js.map +0 -1
- package/dist/valueObject/TenantId.d.ts +0 -10
- package/dist/valueObject/TenantId.js +0 -23
- package/dist/valueObject/TenantId.js.map +0 -1
- package/src/cloudflare/CloudflareKVHttpService.ts +0 -20
- package/src/cloudflare/ImplCloudflareKVHttpService.ts +0 -128
- package/src/cloudflare/__tests__/ImplCloudflareKVHttpService.spec.ts +0 -178
- package/src/config/ConfigurationLoader.ts +0 -72
- package/src/config/__tests__/ConfigurationLoader.spec.ts +0 -62
- package/src/date/DateManipulator.ts +0 -29
- package/src/date/__tests__/DateManipulator.spec.ts +0 -64
- package/src/event/AggregateRoot.ts +0 -5
- package/src/event/DomainEvent.ts +0 -53
- package/src/event/DynamoDBEventMapper.ts +0 -75
- package/src/event/EventHandler.ts +0 -57
- package/src/event/__tests__/DynamoDBEventMapper.spec.ts +0 -121
- package/src/exception/DomainException.ts +0 -34
- package/src/httpClient/FetchHttpClient.ts +0 -92
- package/src/httpClient/HttpClient.ts +0 -46
- package/src/httpClient/HttpRequestBuilder.ts +0 -120
- package/src/httpClient/__tests__/FetchHttpClient.spec.ts +0 -146
- package/src/httpClient/__tests__/HttpClient.spec.ts +0 -52
- package/src/httpClient/__tests__/httpRequestBuilder.spec.ts +0 -75
- package/src/index.ts +0 -37
- package/src/logger/Logger.ts +0 -40
- package/src/logger/LoggerMessage.ts +0 -179
- package/src/logger/LogsHandler.ts +0 -66
- package/src/logger/RemoteLogger.ts +0 -32
- package/src/logger/SquizRemoteLogger.ts +0 -154
- package/src/logger/__tests__/LoggerMessage.spec.ts +0 -147
- package/src/logger/__tests__/LogsHandler.spec.ts +0 -77
- package/src/logger/__tests__/SquizRemoteLogger.spec.ts +0 -185
- package/src/object/__tests__/getProperty.spec.ts +0 -17
- package/src/object/getProperty.ts +0 -21
- package/src/scheduler/EventBridgeScheduler.ts +0 -173
- package/src/scheduler/Scheduler.ts +0 -32
- package/src/scheduler/__tests__/EventBridgeScheduler.spec.ts +0 -311
- package/src/testing/mock.ts +0 -62
- package/src/typesUtils/DynamoDB.ts +0 -17
- package/src/typesUtils/utilities.ts +0 -11
- package/src/validation/handleValidation.ts +0 -13
- package/src/valueObject/TenantId.ts +0 -27
- package/tsconfig.json +0 -13
|
@@ -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
|
-
});
|
package/src/event/DomainEvent.ts
DELETED
|
@@ -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
|
-
}
|
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
EventDynamoDBModel,
|
|
3
|
-
eventToDynamoDB,
|
|
4
|
-
toEventFromPlain,
|
|
5
|
-
toEventFromDynamoDb,
|
|
6
|
-
} from '../DynamoDBEventMapper';
|
|
7
|
-
import { faker } from '@faker-js/faker';
|
|
8
|
-
import { marshall } from '@aws-sdk/util-dynamodb';
|
|
9
|
-
import { TenantIdValue } from '../../valueObject/TenantId';
|
|
10
|
-
import { AsDomainEvent, DomainEvent } from '../DomainEvent';
|
|
11
|
-
|
|
12
|
-
type ExampleEventDetail = {
|
|
13
|
-
experimentId: string;
|
|
14
|
-
tenantId: TenantIdValue;
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
@AsDomainEvent()
|
|
18
|
-
class ExampleEvent implements DomainEvent<{}> {
|
|
19
|
-
readonly detail!: ExampleEventDetail;
|
|
20
|
-
readonly eventId!: string;
|
|
21
|
-
readonly name!: string;
|
|
22
|
-
readonly time!: Date;
|
|
23
|
-
readonly version!: string;
|
|
24
|
-
readonly entityId: string;
|
|
25
|
-
|
|
26
|
-
constructor(data: Omit<ExampleEvent, 'name'>) {
|
|
27
|
-
this.detail = data.detail;
|
|
28
|
-
this.eventId = data.eventId;
|
|
29
|
-
this.name = ExampleEvent.name;
|
|
30
|
-
this.time = data.time;
|
|
31
|
-
this.version = data.version;
|
|
32
|
-
this.entityId = data.entityId;
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const createPlainExampleEvent: () => {
|
|
37
|
-
eventId: string;
|
|
38
|
-
name: string;
|
|
39
|
-
detail: ExampleEventDetail;
|
|
40
|
-
time: string;
|
|
41
|
-
version: string;
|
|
42
|
-
entityId: string;
|
|
43
|
-
} = () => {
|
|
44
|
-
return {
|
|
45
|
-
detail: {
|
|
46
|
-
experimentId: faker.string.uuid(),
|
|
47
|
-
tenantId: faker.string.uuid(),
|
|
48
|
-
},
|
|
49
|
-
eventId: faker.string.uuid(),
|
|
50
|
-
time: faker.date.past().toISOString(),
|
|
51
|
-
version: faker.number.int().toString(),
|
|
52
|
-
name: ExampleEvent.name,
|
|
53
|
-
ttl: faker.date.past().getTime() / 1000,
|
|
54
|
-
entityId: 'example-id',
|
|
55
|
-
};
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
describe('toEventEntityFromDynamoDb', () => {
|
|
59
|
-
it('should map dynamo item to event instance', () => {
|
|
60
|
-
const event = createPlainExampleEvent();
|
|
61
|
-
|
|
62
|
-
const result = toEventFromDynamoDb<ExampleEvent>(marshall(event));
|
|
63
|
-
|
|
64
|
-
expect(result).toEqual(
|
|
65
|
-
new ExampleEvent({
|
|
66
|
-
detail: event.detail,
|
|
67
|
-
eventId: event.eventId,
|
|
68
|
-
time: new Date(event.time),
|
|
69
|
-
version: event.version,
|
|
70
|
-
entityId: 'example-id',
|
|
71
|
-
}),
|
|
72
|
-
);
|
|
73
|
-
});
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
describe('toEventFromPlain', () => {
|
|
77
|
-
const event = createPlainExampleEvent();
|
|
78
|
-
|
|
79
|
-
it('should map the plain object to event instance', () => {
|
|
80
|
-
const result = toEventFromPlain<ExampleEvent>(event);
|
|
81
|
-
|
|
82
|
-
expect(result).toEqual(
|
|
83
|
-
new ExampleEvent({
|
|
84
|
-
detail: event.detail,
|
|
85
|
-
eventId: event.eventId,
|
|
86
|
-
time: new Date(event.time),
|
|
87
|
-
version: event.version,
|
|
88
|
-
entityId: 'example-id',
|
|
89
|
-
}),
|
|
90
|
-
);
|
|
91
|
-
});
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
describe('toDynamoDB', () => {
|
|
95
|
-
it('should map event entity to dynamo item', () => {
|
|
96
|
-
const event = new ExampleEvent({
|
|
97
|
-
detail: {
|
|
98
|
-
experimentId: faker.string.uuid(),
|
|
99
|
-
tenantId: faker.string.uuid(),
|
|
100
|
-
},
|
|
101
|
-
eventId: faker.string.uuid(),
|
|
102
|
-
time: new Date('2020-01-01T00:00:00.000Z'),
|
|
103
|
-
version: faker.number.int().toString(),
|
|
104
|
-
entityId: 'example-id',
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
const result = eventToDynamoDB(event);
|
|
108
|
-
|
|
109
|
-
expect(result).toEqual(
|
|
110
|
-
marshall({
|
|
111
|
-
detail: event.detail,
|
|
112
|
-
eventId: event.eventId,
|
|
113
|
-
time: event.time.toISOString(),
|
|
114
|
-
version: event.version,
|
|
115
|
-
name: 'ExampleEvent',
|
|
116
|
-
ttl: new Date('2020-01-31T00:00:00.000Z').getTime() / 1000,
|
|
117
|
-
entityId: 'example-id',
|
|
118
|
-
} as EventDynamoDBModel),
|
|
119
|
-
);
|
|
120
|
-
});
|
|
121
|
-
});
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
export abstract class DomainException extends Error {
|
|
2
|
-
protected constructor(message: string) {
|
|
3
|
-
super(message);
|
|
4
|
-
|
|
5
|
-
Error.captureStackTrace(this, this.constructor);
|
|
6
|
-
}
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export class ValidationException extends DomainException {
|
|
10
|
-
constructor(
|
|
11
|
-
readonly message = 'Validation failed',
|
|
12
|
-
readonly details?: unknown,
|
|
13
|
-
) {
|
|
14
|
-
super(message);
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export class UnprocessableEntityException extends DomainException {
|
|
19
|
-
constructor(
|
|
20
|
-
readonly message = 'Unprocessable entity',
|
|
21
|
-
readonly details?: unknown,
|
|
22
|
-
) {
|
|
23
|
-
super(message);
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export class NotFoundException extends DomainException {
|
|
28
|
-
constructor(
|
|
29
|
-
readonly message = 'Not found',
|
|
30
|
-
readonly details?: unknown,
|
|
31
|
-
) {
|
|
32
|
-
super(message);
|
|
33
|
-
}
|
|
34
|
-
}
|
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
import { inject, injectable } from 'inversify';
|
|
2
|
-
import { Logger } from '../logger/Logger';
|
|
3
|
-
import { createLog } from '../logger/LoggerMessage';
|
|
4
|
-
import fetch from 'node-fetch-commonjs';
|
|
5
|
-
import {
|
|
6
|
-
HttpClient,
|
|
7
|
-
HttpRequestOptions,
|
|
8
|
-
HttpResponse,
|
|
9
|
-
} from '../httpClient/HttpClient';
|
|
10
|
-
import { captureHTTPsGlobal } from 'aws-xray-sdk';
|
|
11
|
-
import http from 'http';
|
|
12
|
-
import https from 'https';
|
|
13
|
-
|
|
14
|
-
captureHTTPsGlobal(https, true);
|
|
15
|
-
captureHTTPsGlobal(http, true);
|
|
16
|
-
|
|
17
|
-
@injectable()
|
|
18
|
-
export class FetchHttpClient implements HttpClient {
|
|
19
|
-
constructor(
|
|
20
|
-
@inject(Logger)
|
|
21
|
-
private readonly logger: Logger,
|
|
22
|
-
) {}
|
|
23
|
-
|
|
24
|
-
async sendRequest({
|
|
25
|
-
headers = {},
|
|
26
|
-
url,
|
|
27
|
-
method,
|
|
28
|
-
body,
|
|
29
|
-
handleHttpErrorStatusCodes,
|
|
30
|
-
}: HttpRequestOptions): Promise<HttpResponse> {
|
|
31
|
-
const logMessage = createLog()
|
|
32
|
-
.attachMetadata({ url: url.toString() })
|
|
33
|
-
.attachMetadata({ method })
|
|
34
|
-
.create(this);
|
|
35
|
-
const applicationJsonHeader = 'application/json';
|
|
36
|
-
const isApplicationJson = headers['Content-Type'] === applicationJsonHeader;
|
|
37
|
-
const requestBody = body && isApplicationJson ? JSON.stringify(body) : body;
|
|
38
|
-
|
|
39
|
-
this.logger.debug(...logMessage('started sending http request'));
|
|
40
|
-
const response = await fetch(url, {
|
|
41
|
-
headers,
|
|
42
|
-
method,
|
|
43
|
-
...(body && { body: requestBody }),
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
this.logger.debug(...logMessage('finished sending http request'));
|
|
47
|
-
|
|
48
|
-
const responseContentType = response.headers.get('Content-Type');
|
|
49
|
-
const isJsonResponse = responseContentType?.includes(applicationJsonHeader);
|
|
50
|
-
|
|
51
|
-
this.logger.debug(
|
|
52
|
-
...logMessage(
|
|
53
|
-
`the response ` +
|
|
54
|
-
`${isJsonResponse ? 'contains' : 'does not contain'} ` +
|
|
55
|
-
`the application/json header`,
|
|
56
|
-
),
|
|
57
|
-
);
|
|
58
|
-
this.logger.debug(...logMessage('available headers in the next log:'));
|
|
59
|
-
this.logger.debug(...logMessage(response.headers));
|
|
60
|
-
const responseBodyRawText = await response.text();
|
|
61
|
-
const responseBody =
|
|
62
|
-
isJsonResponse && responseBodyRawText
|
|
63
|
-
? JSON.parse(responseBodyRawText)
|
|
64
|
-
: responseBodyRawText;
|
|
65
|
-
const httpResponse = new HttpResponse({
|
|
66
|
-
statusCode: response.status,
|
|
67
|
-
body: responseBody,
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
const shouldBeHandled = handleHttpErrorStatusCodes?.includes(
|
|
71
|
-
response.status,
|
|
72
|
-
);
|
|
73
|
-
|
|
74
|
-
if (shouldBeHandled) {
|
|
75
|
-
this.logger.debug(...logMessage('handled NotFound as an empty response'));
|
|
76
|
-
return httpResponse;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
if (!response.ok) {
|
|
80
|
-
this.logger.error(
|
|
81
|
-
...logMessage(`http response status: ${response.status}`),
|
|
82
|
-
);
|
|
83
|
-
this.logger.debug(...logMessage(`http response body in the next log`));
|
|
84
|
-
this.logger.debug(...logMessage({ responseBodyRawText }));
|
|
85
|
-
throw new Error(`Error occurs calling: ${method} ${url}`, {
|
|
86
|
-
cause: responseBody,
|
|
87
|
-
});
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
return httpResponse;
|
|
91
|
-
}
|
|
92
|
-
}
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import { injectable } from 'inversify';
|
|
2
|
-
import { captureHTTPsGlobal } from 'aws-xray-sdk';
|
|
3
|
-
import { ClassFields } from './../typesUtils/utilities';
|
|
4
|
-
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
5
|
-
captureHTTPsGlobal(require('https'));
|
|
6
|
-
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
7
|
-
captureHTTPsGlobal(require('http'));
|
|
8
|
-
|
|
9
|
-
export type HttpRequestOptions = {
|
|
10
|
-
url: URL | string;
|
|
11
|
-
method: HttpMethod;
|
|
12
|
-
body?: string | Record<string, string> | Array<unknown>;
|
|
13
|
-
headers?: Record<string, string>;
|
|
14
|
-
handleHttpErrorStatusCodes?: Array<number>;
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
export enum HttpMethod {
|
|
18
|
-
GET = 'GET',
|
|
19
|
-
POST = 'POST',
|
|
20
|
-
DELETE = 'DELETE',
|
|
21
|
-
PUT = 'PUT',
|
|
22
|
-
PATCH = 'PATCH',
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export class HttpResponse {
|
|
26
|
-
readonly statusCode: number;
|
|
27
|
-
readonly body: unknown;
|
|
28
|
-
|
|
29
|
-
constructor(opts: ClassFields<HttpResponse>) {
|
|
30
|
-
this.statusCode = opts.statusCode;
|
|
31
|
-
this.body = opts.body;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
hasHttpStatus(statusCode: number): boolean {
|
|
35
|
-
return this.statusCode === statusCode;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
isNotFound(): boolean {
|
|
39
|
-
return this.hasHttpStatus(404);
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
@injectable()
|
|
44
|
-
export abstract class HttpClient {
|
|
45
|
-
abstract sendRequest(opts: HttpRequestOptions): Promise<HttpResponse>;
|
|
46
|
-
}
|
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
import { injectable } from 'inversify';
|
|
2
|
-
import { HttpClient, HttpRequestOptions, HttpResponse } from './HttpClient';
|
|
3
|
-
|
|
4
|
-
@injectable()
|
|
5
|
-
export class HttpRequestBuilderFactory {
|
|
6
|
-
constructor(private readonly httpClient: HttpClient) {}
|
|
7
|
-
|
|
8
|
-
create(): Pick<HttpRequestBuilder, 'url'> {
|
|
9
|
-
return new HttpRequestBuilder({}, this.httpClient);
|
|
10
|
-
}
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export class HttpRequestBuilder {
|
|
14
|
-
constructor(
|
|
15
|
-
private opts: Partial<HttpRequestOptions>,
|
|
16
|
-
private readonly httpClient: HttpClient,
|
|
17
|
-
) {}
|
|
18
|
-
|
|
19
|
-
url(url: HttpRequestOptions['url']): Pick<HttpRequestBuilder, 'method'> {
|
|
20
|
-
return this.setOptions({
|
|
21
|
-
...this.opts,
|
|
22
|
-
url,
|
|
23
|
-
});
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
method(method: HttpRequestOptions['method']): HttpRequestBuilder {
|
|
27
|
-
return this.setOptions({
|
|
28
|
-
...this.opts,
|
|
29
|
-
method,
|
|
30
|
-
});
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
headers(headers: HttpRequestOptions['headers']): HttpRequestBuilder {
|
|
34
|
-
return this.setOptions({
|
|
35
|
-
...this.opts,
|
|
36
|
-
headers: {
|
|
37
|
-
...this.opts.headers,
|
|
38
|
-
...headers,
|
|
39
|
-
},
|
|
40
|
-
});
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
body(body?: HttpRequestOptions['body']): HttpRequestBuilder {
|
|
44
|
-
return this.setOptions({
|
|
45
|
-
...this.opts,
|
|
46
|
-
body,
|
|
47
|
-
});
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
authorizationByBearer(token: string): HttpRequestBuilder {
|
|
51
|
-
return this.setOptions({
|
|
52
|
-
...this.opts,
|
|
53
|
-
headers: {
|
|
54
|
-
...this.opts.headers,
|
|
55
|
-
Authorization: `Bearer ${token}`,
|
|
56
|
-
},
|
|
57
|
-
});
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
authorizationByXApiKey(apiKey: string): HttpRequestBuilder {
|
|
61
|
-
return this.setOptions({
|
|
62
|
-
...this.opts,
|
|
63
|
-
headers: {
|
|
64
|
-
...this.opts.headers,
|
|
65
|
-
'x-api-key': apiKey,
|
|
66
|
-
},
|
|
67
|
-
});
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
applicationJson(): HttpRequestBuilder {
|
|
71
|
-
return this.setOptions({
|
|
72
|
-
...this.opts,
|
|
73
|
-
headers: {
|
|
74
|
-
...this.opts.headers,
|
|
75
|
-
'Content-Type': 'application/json',
|
|
76
|
-
},
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
handleNotFound(): HttpRequestBuilder {
|
|
81
|
-
return this.handleErrorStatusCode(404);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
handleErrorStatusCode(errorStatusCode: number): HttpRequestBuilder {
|
|
85
|
-
return this.setOptions({
|
|
86
|
-
...this.opts,
|
|
87
|
-
handleHttpErrorStatusCodes: [
|
|
88
|
-
...new Set([
|
|
89
|
-
...(this.opts.handleHttpErrorStatusCodes ?? []),
|
|
90
|
-
errorStatusCode,
|
|
91
|
-
]),
|
|
92
|
-
],
|
|
93
|
-
});
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
private setOptions(opts: Partial<HttpRequestOptions>): HttpRequestBuilder {
|
|
97
|
-
this.opts = opts;
|
|
98
|
-
return this;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
sendRequest(): Promise<HttpResponse> {
|
|
102
|
-
if (!this.opts.url) {
|
|
103
|
-
throw new Error('The url is not defined');
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
if (!this.opts.method) {
|
|
107
|
-
throw new Error('The method is not defined');
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// this is only for typescript information, without extra variable typescript thinks that
|
|
111
|
-
// the url and method are undefined
|
|
112
|
-
const options: HttpRequestOptions = {
|
|
113
|
-
url: this.opts.url,
|
|
114
|
-
method: this.opts.method,
|
|
115
|
-
...this.opts,
|
|
116
|
-
};
|
|
117
|
-
|
|
118
|
-
return this.httpClient.sendRequest(options);
|
|
119
|
-
}
|
|
120
|
-
}
|