@squiz/optimization-utils 1.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 (108) hide show
  1. package/dist/cloudflare/CloudflareKVHttpService.d.ts +11 -0
  2. package/dist/cloudflare/CloudflareKVHttpService.js +19 -0
  3. package/dist/cloudflare/CloudflareKVHttpService.js.map +1 -0
  4. package/dist/cloudflare/ImplCloudflareKVHttpService.d.ts +22 -0
  5. package/dist/cloudflare/ImplCloudflareKVHttpService.js +96 -0
  6. package/dist/cloudflare/ImplCloudflareKVHttpService.js.map +1 -0
  7. package/dist/config/ConfigurationLoader.d.ts +23 -0
  8. package/dist/config/ConfigurationLoader.js +99 -0
  9. package/dist/config/ConfigurationLoader.js.map +1 -0
  10. package/dist/date/DateManipulator.d.ts +6 -0
  11. package/dist/date/DateManipulator.js +21 -0
  12. package/dist/date/DateManipulator.js.map +1 -0
  13. package/dist/event/AggregateRoot.d.ts +4 -0
  14. package/dist/event/AggregateRoot.js +3 -0
  15. package/dist/event/AggregateRoot.js.map +1 -0
  16. package/dist/event/DomainEvent.d.ts +13 -0
  17. package/dist/event/DomainEvent.js +28 -0
  18. package/dist/event/DomainEvent.js.map +1 -0
  19. package/dist/event/DynamoDBEventMapper.d.ts +36 -0
  20. package/dist/event/DynamoDBEventMapper.js +55 -0
  21. package/dist/event/DynamoDBEventMapper.js.map +1 -0
  22. package/dist/event/EventHandler.d.ts +14 -0
  23. package/dist/event/EventHandler.js +43 -0
  24. package/dist/event/EventHandler.js.map +1 -0
  25. package/dist/exception/DomainException.d.ts +18 -0
  26. package/dist/exception/DomainException.js +41 -0
  27. package/dist/exception/DomainException.js.map +1 -0
  28. package/dist/httpClient/FetchHttpClient.d.ts +7 -0
  29. package/dist/httpClient/FetchHttpClient.js +86 -0
  30. package/dist/httpClient/FetchHttpClient.js.map +1 -0
  31. package/dist/httpClient/HttpClient.d.ts +25 -0
  32. package/dist/httpClient/HttpClient.js +45 -0
  33. package/dist/httpClient/HttpClient.js.map +1 -0
  34. package/dist/httpClient/HttpRequestBuilder.d.ts +22 -0
  35. package/dist/httpClient/HttpRequestBuilder.js +126 -0
  36. package/dist/httpClient/HttpRequestBuilder.js.map +1 -0
  37. package/dist/logger/Logger.d.ts +10 -0
  38. package/dist/logger/Logger.js +30 -0
  39. package/dist/logger/Logger.js.map +1 -0
  40. package/dist/logger/LoggerMessage.d.ts +27 -0
  41. package/dist/logger/LoggerMessage.js +94 -0
  42. package/dist/logger/LoggerMessage.js.map +1 -0
  43. package/dist/logger/RemoteLogger.d.ts +30 -0
  44. package/dist/logger/RemoteLogger.js +35 -0
  45. package/dist/logger/RemoteLogger.js.map +1 -0
  46. package/dist/logger/SquizRemoteLogger.d.ts +53 -0
  47. package/dist/logger/SquizRemoteLogger.js +128 -0
  48. package/dist/logger/SquizRemoteLogger.js.map +1 -0
  49. package/dist/package.d.ts +23 -0
  50. package/dist/package.js +40 -0
  51. package/dist/package.js.map +1 -0
  52. package/dist/scheduler/EventBridgeScheduler.d.ts +21 -0
  53. package/dist/scheduler/EventBridgeScheduler.js +131 -0
  54. package/dist/scheduler/EventBridgeScheduler.js.map +1 -0
  55. package/dist/scheduler/Scheduler.d.ts +21 -0
  56. package/dist/scheduler/Scheduler.js +17 -0
  57. package/dist/scheduler/Scheduler.js.map +1 -0
  58. package/dist/testing/mock.d.ts +12 -0
  59. package/dist/testing/mock.js +51 -0
  60. package/dist/testing/mock.js.map +1 -0
  61. package/dist/typesUtils/DynamoDB.d.ts +4 -0
  62. package/dist/typesUtils/DynamoDB.js +3 -0
  63. package/dist/typesUtils/DynamoDB.js.map +1 -0
  64. package/dist/typesUtils/utilities.d.ts +9 -0
  65. package/dist/typesUtils/utilities.js +3 -0
  66. package/dist/typesUtils/utilities.js.map +1 -0
  67. package/dist/validation/handleValidation.d.ts +2 -0
  68. package/dist/validation/handleValidation.js +11 -0
  69. package/dist/validation/handleValidation.js.map +1 -0
  70. package/dist/valueObject/TenantId.d.ts +10 -0
  71. package/dist/valueObject/TenantId.js +23 -0
  72. package/dist/valueObject/TenantId.js.map +1 -0
  73. package/package.json +26 -0
  74. package/src/cloudflare/CloudflareKVHttpService.ts +20 -0
  75. package/src/cloudflare/ImplCloudflareKVHttpService.ts +128 -0
  76. package/src/cloudflare/__tests__/ImplCloudflareKVHttpService.spec.ts +178 -0
  77. package/src/config/ConfigurationLoader.ts +72 -0
  78. package/src/config/__tests__/ConfigurationLoader.spec.ts +62 -0
  79. package/src/date/DateManipulator.ts +29 -0
  80. package/src/date/__tests__/DateManipulator.spec.ts +64 -0
  81. package/src/event/AggregateRoot.ts +5 -0
  82. package/src/event/DomainEvent.ts +52 -0
  83. package/src/event/DynamoDBEventMapper.ts +72 -0
  84. package/src/event/EventHandler.ts +57 -0
  85. package/src/event/__tests__/DynamoDBEventMapper.spec.ts +113 -0
  86. package/src/exception/DomainException.ts +34 -0
  87. package/src/httpClient/FetchHttpClient.ts +92 -0
  88. package/src/httpClient/HttpClient.ts +46 -0
  89. package/src/httpClient/HttpRequestBuilder.ts +120 -0
  90. package/src/httpClient/__tests__/FetchHttpClient.spec.ts +146 -0
  91. package/src/httpClient/__tests__/HttpClient.spec.ts +52 -0
  92. package/src/httpClient/__tests__/httpRequestBuilder.spec.ts +75 -0
  93. package/src/logger/Logger.ts +40 -0
  94. package/src/logger/LoggerMessage.ts +151 -0
  95. package/src/logger/RemoteLogger.ts +32 -0
  96. package/src/logger/SquizRemoteLogger.ts +157 -0
  97. package/src/logger/__tests__/LoggerMessage.spec.ts +133 -0
  98. package/src/logger/__tests__/SquizRemoteLogger.spec.ts +185 -0
  99. package/src/package.ts +23 -0
  100. package/src/scheduler/EventBridgeScheduler.ts +177 -0
  101. package/src/scheduler/Scheduler.ts +32 -0
  102. package/src/scheduler/__tests__/EventBridgeScheduler.spec.ts +310 -0
  103. package/src/testing/mock.ts +62 -0
  104. package/src/typesUtils/DynamoDB.ts +17 -0
  105. package/src/typesUtils/utilities.ts +11 -0
  106. package/src/validation/handleValidation.ts +13 -0
  107. package/src/valueObject/TenantId.ts +27 -0
  108. package/tsconfig.json +13 -0
@@ -0,0 +1,310 @@
1
+ import { Container } from 'inversify';
2
+ import {
3
+ CreateScheduleCommand,
4
+ CreateScheduleCommandInput,
5
+ DeleteScheduleCommand,
6
+ DeleteScheduleCommandInput,
7
+ GetScheduleCommand,
8
+ GetScheduleCommandInput,
9
+ ResourceNotFoundException,
10
+ SchedulerClient,
11
+ } from '@aws-sdk/client-scheduler';
12
+ import { randomUUID } from 'crypto';
13
+ import { faker } from '@faker-js/faker';
14
+ import { ExceptionOptionType } from '@smithy/smithy-client/dist-types/exceptions';
15
+ import { SchedulerServiceException } from '@aws-sdk/client-scheduler/dist-types/models/SchedulerServiceException';
16
+ import { Logger } from '../../logger/Logger';
17
+ import {
18
+ createLoggerMock,
19
+ createSchedulerClientMock,
20
+ } from '../../testing/mock';
21
+ import { eventToDynamoDB } from '../../event/DynamoDBEventMapper';
22
+ import { Scheduler } from '../Scheduler';
23
+ import {
24
+ EventBridgeScheduler,
25
+ EventBridgeSchedulerConfig,
26
+ } from '../EventBridgeScheduler';
27
+ import { DomainEvent } from '../../event/DomainEvent';
28
+
29
+ jest.mock('@aws-sdk/client-scheduler', () => {
30
+ return {
31
+ ...jest.requireActual('@aws-sdk/client-scheduler'),
32
+ CreateScheduleCommand: jest.fn(),
33
+ GetScheduleCommand: jest.fn(),
34
+ DeleteScheduleCommand: jest.fn(),
35
+ };
36
+ });
37
+
38
+ class ExampleEvent implements DomainEvent {
39
+ readonly detail: Record<string, unknown> = {};
40
+ readonly eventId: string = randomUUID();
41
+ readonly name: string = ExampleEvent.name;
42
+ readonly time: Date = new Date();
43
+ readonly version: string = '1';
44
+ }
45
+
46
+ describe('EventBridgeScheduler', () => {
47
+ let scheduler: Scheduler;
48
+ let schedulerClient: SchedulerClient;
49
+ let event: ExampleEvent;
50
+ const config: EventBridgeSchedulerConfig = {
51
+ eventTableName: 'event-table',
52
+ groupName: 'experiment',
53
+ region: 'eu-north-1',
54
+ schedulerRole: 'example-arn',
55
+ };
56
+
57
+ beforeEach(() => {
58
+ jest.resetAllMocks();
59
+
60
+ const container = new Container({ defaultScope: 'Singleton' });
61
+
62
+ container.bind(Scheduler).toDynamicValue(({ container: c }) => {
63
+ return new EventBridgeScheduler(
64
+ config,
65
+ c.get(SchedulerClient),
66
+ c.get(Logger),
67
+ );
68
+ });
69
+ container
70
+ .bind(SchedulerClient)
71
+ .toConstantValue(createSchedulerClientMock());
72
+ container.bind(Logger).toConstantValue(createLoggerMock());
73
+
74
+ event = new ExampleEvent();
75
+ schedulerClient = container.get(SchedulerClient);
76
+ scheduler = container.get(Scheduler);
77
+ });
78
+
79
+ describe('schedule', () => {
80
+ it('should call the CreateScheduleCommand with the PutItemCommandInput', async () => {
81
+ await scheduler.schedule(event, {
82
+ type: 'at',
83
+ value: faker.date.future(),
84
+ name: randomUUID(),
85
+ });
86
+
87
+ expect(schedulerClient.send).toHaveBeenCalledWith(
88
+ expect.any(CreateScheduleCommand),
89
+ );
90
+ expect(CreateScheduleCommand).toHaveBeenCalledWith(
91
+ expect.objectContaining({
92
+ Target: {
93
+ Arn: 'arn:aws:scheduler:::aws-sdk:dynamodb:putItem',
94
+ Input: JSON.stringify({
95
+ TableName: config.eventTableName,
96
+ Item: {
97
+ ...eventToDynamoDB(event),
98
+ eventId: {
99
+ S: '<aws.scheduler.execution-id>',
100
+ },
101
+ time: {
102
+ S: '<aws.scheduler.scheduled-time>',
103
+ },
104
+ },
105
+ }),
106
+ RoleArn: config.schedulerRole,
107
+ },
108
+ } as CreateScheduleCommandInput),
109
+ );
110
+ });
111
+
112
+ it('should schedule with the "at" schedule expression', async () => {
113
+ await scheduler.schedule(event, {
114
+ type: 'at',
115
+ value: new Date('2023-01-01T00:00:00.000Z'),
116
+ name: randomUUID(),
117
+ });
118
+
119
+ expect(schedulerClient.send).toHaveBeenCalledWith(
120
+ expect.any(CreateScheduleCommand),
121
+ );
122
+ expect(CreateScheduleCommand).toHaveBeenCalledWith(
123
+ expect.objectContaining({
124
+ ScheduleExpression: 'at(2023-01-01T00:00:00)',
125
+ ScheduleExpressionTimezone: 'UTC',
126
+ } as CreateScheduleCommandInput),
127
+ );
128
+ });
129
+
130
+ it('should schedule with the "rate" schedule expression', async () => {
131
+ await scheduler.schedule(event, {
132
+ type: 'rate',
133
+ value: 15,
134
+ unit: 'days',
135
+ name: randomUUID(),
136
+ });
137
+
138
+ expect(schedulerClient.send).toHaveBeenCalledWith(
139
+ expect.any(CreateScheduleCommand),
140
+ );
141
+ expect(CreateScheduleCommand).toHaveBeenCalledWith(
142
+ expect.objectContaining({
143
+ ScheduleExpression: 'rate(15 days)',
144
+ ScheduleExpressionTimezone: 'UTC',
145
+ } as CreateScheduleCommandInput),
146
+ );
147
+ });
148
+
149
+ it('should schedule with the "cron" schedule expression', async () => {
150
+ await scheduler.schedule(event, {
151
+ type: 'cron',
152
+ value: '* * * * ? *',
153
+ name: randomUUID(),
154
+ });
155
+
156
+ expect(schedulerClient.send).toHaveBeenCalledWith(
157
+ expect.any(CreateScheduleCommand),
158
+ );
159
+ expect(CreateScheduleCommand).toHaveBeenCalledWith(
160
+ expect.objectContaining({
161
+ ScheduleExpression: 'cron(* * * * ? *)',
162
+ ScheduleExpressionTimezone: 'UTC',
163
+ } as CreateScheduleCommandInput),
164
+ );
165
+ });
166
+
167
+ it('should create a schedule name which includes the event name', async () => {
168
+ await scheduler.schedule(event, {
169
+ type: 'cron',
170
+ value: '* * * * ? *',
171
+ name: 'ExampleSchedule',
172
+ });
173
+
174
+ expect(schedulerClient.send).toHaveBeenCalledWith(
175
+ expect.any(CreateScheduleCommand),
176
+ );
177
+ expect(CreateScheduleCommand).toHaveBeenCalledWith(
178
+ expect.objectContaining({
179
+ Name: 'ExampleSchedule',
180
+ } as CreateScheduleCommandInput),
181
+ );
182
+ });
183
+
184
+ it('should trim end if the name contains more than 64 characters', async () => {
185
+ await scheduler.schedule(event, {
186
+ type: 'cron',
187
+ value: '* * * * ? *',
188
+ name: 'pVlH%46KTS).@KFhaumnG!#>dcZ#FTh=yBwD$rwDz@s41BO5C=?>gr9J.#[)!T^4Y',
189
+ });
190
+
191
+ expect(schedulerClient.send).toHaveBeenCalledWith(
192
+ expect.any(CreateScheduleCommand),
193
+ );
194
+ expect(CreateScheduleCommand).toHaveBeenCalledWith(
195
+ expect.objectContaining({
196
+ Name: 'pVlH%46KTS).@KFhaumnG!#>dcZ#FTh=yBwD$rwDz@s41BO5C=?>gr9J.#[)!T^4',
197
+ } as CreateScheduleCommandInput),
198
+ );
199
+ });
200
+
201
+ it('should call CreateScheduleCommand with correct params', async () => {
202
+ await scheduler.schedule(event, {
203
+ type: 'cron',
204
+ value: '* * * * ? *',
205
+ name: randomUUID(),
206
+ });
207
+
208
+ expect(schedulerClient.send).toHaveBeenCalledWith(
209
+ expect.any(CreateScheduleCommand),
210
+ );
211
+ expect(CreateScheduleCommand).toHaveBeenCalledWith(
212
+ expect.objectContaining({
213
+ GroupName: config.groupName,
214
+ State: 'ENABLED',
215
+ FlexibleTimeWindow: {
216
+ Mode: 'OFF',
217
+ },
218
+ ActionAfterCompletion: 'DELETE',
219
+ ClientToken: expect.any(String),
220
+ } as CreateScheduleCommandInput),
221
+ );
222
+ });
223
+
224
+ it('should remove the existing schedule if it exists', async () => {
225
+ const scheduleName = randomUUID();
226
+
227
+ await scheduler.schedule(event, {
228
+ type: 'at',
229
+ value: faker.date.future(),
230
+ name: scheduleName,
231
+ });
232
+
233
+ expect(GetScheduleCommand).toHaveBeenCalledWith({
234
+ GroupName: config.groupName,
235
+ Name: scheduleName,
236
+ } as GetScheduleCommandInput);
237
+ expect(DeleteScheduleCommand).toHaveBeenCalledWith({
238
+ GroupName: config.groupName,
239
+ Name: scheduleName,
240
+ } as DeleteScheduleCommandInput);
241
+ });
242
+ });
243
+
244
+ describe('deleteSchedule', () => {
245
+ it('should delete schedule if it exists', async () => {
246
+ const scheduleName = 'example-schedule';
247
+
248
+ await scheduler.deleteSchedule(scheduleName);
249
+
250
+ expect(GetScheduleCommand).toHaveBeenCalledWith({
251
+ GroupName: config.groupName,
252
+ Name: scheduleName,
253
+ } as GetScheduleCommandInput);
254
+ expect(DeleteScheduleCommand).toHaveBeenCalledWith({
255
+ GroupName: config.groupName,
256
+ Name: scheduleName,
257
+ } as DeleteScheduleCommandInput);
258
+ });
259
+
260
+ it('should skip deleting schedule if it does not exist', async () => {
261
+ (schedulerClient.send as jest.Mock).mockRejectedValueOnce(
262
+ new ResourceNotFoundException(
263
+ {} as ExceptionOptionType<
264
+ ResourceNotFoundException,
265
+ SchedulerServiceException
266
+ >,
267
+ ),
268
+ );
269
+ const scheduleName = randomUUID();
270
+
271
+ await scheduler.schedule(event, {
272
+ type: 'at',
273
+ value: faker.date.future(),
274
+ name: scheduleName,
275
+ });
276
+
277
+ expect(GetScheduleCommand).toHaveBeenCalledTimes(1);
278
+ expect(DeleteScheduleCommand).toHaveBeenCalledTimes(0);
279
+ });
280
+
281
+ it('should rethrow exception if the GetScheduleCommand return exception other than ResourceNotFound', async () => {
282
+ (schedulerClient.send as jest.Mock).mockRejectedValueOnce(new Error());
283
+ const scheduleName = randomUUID();
284
+
285
+ await expect(() =>
286
+ scheduler.deleteSchedule(scheduleName),
287
+ ).rejects.toThrow();
288
+ });
289
+
290
+ it('should trim the scheduleName up to 64 characters', async () => {
291
+ const scheduleNameWith65Characters =
292
+ 'pVlH%46KTS).@KFhaumnG!#>dcZ#FTh=yBwD$rwDz@s41BO5C=?>gr9J.#[)!T^4Y';
293
+ const expectedScheduleName =
294
+ 'pVlH%46KTS).@KFhaumnG!#>dcZ#FTh=yBwD$rwDz@s41BO5C=?>gr9J.#[)!T^4';
295
+
296
+ await scheduler.deleteSchedule(scheduleNameWith65Characters);
297
+
298
+ expect(GetScheduleCommand).toHaveBeenCalledWith(
299
+ expect.objectContaining({
300
+ Name: expectedScheduleName,
301
+ }),
302
+ ),
303
+ expect(DeleteScheduleCommand).toHaveBeenCalledWith(
304
+ expect.objectContaining({
305
+ Name: expectedScheduleName,
306
+ }),
307
+ );
308
+ });
309
+ });
310
+ });
@@ -0,0 +1,62 @@
1
+ import { HttpClient } from '../httpClient/HttpClient';
2
+ import { Logger } from '../logger/Logger';
3
+ import { RemoteLogger } from '../logger/RemoteLogger';
4
+ import { SchedulerClient } from '@aws-sdk/client-scheduler';
5
+ import { Scheduler } from '../scheduler/Scheduler';
6
+
7
+ export type FetchMock = jest.Mock<
8
+ ReturnType<typeof fetch>,
9
+ Parameters<typeof fetch>
10
+ >;
11
+
12
+ export const createResponse = <T>(
13
+ status: number,
14
+ body?: T,
15
+ headers?: Record<string, string>,
16
+ ): Response =>
17
+ ({
18
+ status,
19
+ ok: status < 400,
20
+ headers: new Map(Object.entries(headers ?? {})) as unknown as Headers,
21
+ json: async () => body,
22
+ text: async () => JSON.stringify(body),
23
+ }) as Response;
24
+
25
+ export const createHttpClientMock = (): HttpClient => {
26
+ return {
27
+ sendRequest: jest.fn(),
28
+ };
29
+ };
30
+
31
+ export const createLoggerMock = (): Logger => {
32
+ return {
33
+ debug: jest.fn(),
34
+ error: jest.fn(),
35
+ info: jest.fn(),
36
+ log: jest.fn(),
37
+ verbose: jest.fn(),
38
+ warn: jest.fn(),
39
+ };
40
+ };
41
+
42
+ export const createRemoteLoggerMock = (): RemoteLogger => {
43
+ return {
44
+ log: jest.fn(),
45
+ info: jest.fn(),
46
+ warn: jest.fn(),
47
+ error: jest.fn(),
48
+ };
49
+ };
50
+
51
+ export const createSchedulerClientMock = (): SchedulerClient => {
52
+ return {
53
+ send: jest.fn(),
54
+ } as unknown as SchedulerClient;
55
+ };
56
+
57
+ export const createSchedulerMock = (): Scheduler => {
58
+ return {
59
+ schedule: jest.fn(),
60
+ deleteSchedule: jest.fn(),
61
+ };
62
+ };
@@ -0,0 +1,17 @@
1
+ import { AttributeValue } from '@aws-sdk/client-dynamodb';
2
+
3
+ export type MarshalledResult<T> = {
4
+ [K in keyof T]: T[K] extends string
5
+ ? AttributeValue.SMember
6
+ : T[K] extends number
7
+ ? AttributeValue.NMember
8
+ : T[K] extends Record<string, unknown>
9
+ ? AttributeValue.MMember
10
+ : T[K] extends Array<unknown>
11
+ ? AttributeValue.LMember
12
+ : T[K] extends boolean
13
+ ? AttributeValue.BOOLMember
14
+ : T[K] extends undefined
15
+ ? Partial<T[K]>
16
+ : never;
17
+ };
@@ -0,0 +1,11 @@
1
+ type MethodKeysOrNever<T> = {
2
+ [K in keyof T]: T[K] extends Function ? K : never;
3
+ };
4
+ export type MethodKeys<T> = MethodKeysOrNever<T>[keyof T];
5
+ export type ClassFields<T> = Omit<T, Exclude<MethodKeys<T>, undefined>>;
6
+ export type OverwriteValueOf<TInstance, DValueOfReturn> = Omit<
7
+ TInstance,
8
+ 'valueOf'
9
+ > & {
10
+ valueOf(): DValueOfReturn;
11
+ };
@@ -0,0 +1,13 @@
1
+ import { SafeParseReturnType } from 'zod';
2
+ import { ValidationException } from '../exception/DomainException';
3
+
4
+ export function handleValidation<T, O>(
5
+ safeParseResult: SafeParseReturnType<T, O>,
6
+ errorMessage = 'Validation Failed',
7
+ ): O {
8
+ if (!safeParseResult.success) {
9
+ throw new ValidationException(errorMessage, safeParseResult.error.issues);
10
+ }
11
+
12
+ return safeParseResult.data;
13
+ }
@@ -0,0 +1,27 @@
1
+ import { z } from 'zod';
2
+ import { handleValidation } from '../validation/handleValidation';
3
+
4
+ export const TENANT_ID_SCHEMA = z.string().min(1);
5
+
6
+ export type TenantIdValue = z.infer<typeof TENANT_ID_SCHEMA>;
7
+
8
+ export class TenantId {
9
+ static parse(data: unknown): TenantId {
10
+ return new TenantId(
11
+ handleValidation(
12
+ TENANT_ID_SCHEMA.safeParse(data),
13
+ 'The validation of tenant id failed',
14
+ ),
15
+ );
16
+ }
17
+
18
+ constructor(private readonly value: TenantIdValue) {}
19
+
20
+ valueOf(): TenantIdValue {
21
+ return this.value;
22
+ }
23
+
24
+ equals(tenantId: TenantId): boolean {
25
+ return this.value === tenantId.value;
26
+ }
27
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,13 @@
1
+ {
2
+ "extends": "@tsconfig/node20/tsconfig.json",
3
+ "compilerOptions": {
4
+ "experimentalDecorators": true,
5
+ "emitDecoratorMetadata": true,
6
+ "sourceMap": true,
7
+ "declaration": true,
8
+ "outDir": "dist",
9
+ "baseUrl": "."
10
+ },
11
+ "include": ["src"],
12
+ "exclude": ["dist", "**/*.spec.ts"]
13
+ }