codeweaver 3.1.3 → 4.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 (79) hide show
  1. package/README.md +56 -73
  2. package/package.json +23 -1
  3. package/src/config.ts +17 -15
  4. package/src/constants.ts +1 -0
  5. package/src/core/aws/api-gateway.ts +187 -0
  6. package/src/core/aws/basic-types.ts +147 -0
  7. package/src/core/aws/dynamodb.ts +187 -0
  8. package/src/core/aws/index.ts +9 -0
  9. package/src/core/aws/lambda.ts +199 -0
  10. package/src/core/aws/message-broker.ts +167 -0
  11. package/src/core/aws/message.ts +259 -0
  12. package/src/core/aws/s3.ts +136 -0
  13. package/src/core/aws/utilities.ts +44 -0
  14. package/src/core/cache/basic-types.ts +17 -0
  15. package/src/core/cache/decorator.ts +72 -0
  16. package/src/core/cache/index.ts +4 -0
  17. package/src/core/cache/memory-cache.class.ts +119 -0
  18. package/src/{utilities/cache/redis-cache.ts → core/cache/redis-cache.class.ts} +58 -10
  19. package/src/core/container/basic-types.ts +10 -0
  20. package/src/{utilities → core/container}/container.ts +7 -17
  21. package/src/core/container/index.ts +2 -0
  22. package/src/{utilities → core/error}/error-handling.ts +1 -65
  23. package/src/core/error/index.ts +3 -0
  24. package/src/core/error/response-error.ts +45 -0
  25. package/src/core/error/send-http-error.ts +15 -0
  26. package/src/core/file/file-helpers.ts +166 -0
  27. package/src/core/file/index.ts +1 -0
  28. package/src/{utilities → core/helpers}/assignment.ts +2 -2
  29. package/src/core/helpers/comparison.ts +86 -0
  30. package/src/{utilities → core/helpers}/conversion.ts +2 -2
  31. package/src/core/helpers/decorators.ts +316 -0
  32. package/src/core/helpers/format.ts +9 -0
  33. package/src/core/helpers/index.ts +7 -0
  34. package/src/core/helpers/range.ts +67 -0
  35. package/src/core/helpers/types.ts +3 -0
  36. package/src/core/logger/index.ts +4 -0
  37. package/src/{utilities/logger/logger.config.ts → core/logger/winston-logger.config.ts} +1 -1
  38. package/src/{utilities → core}/logger/winston-logger.service.ts +3 -3
  39. package/src/core/message-broker/bullmq/basic-types.ts +67 -0
  40. package/src/core/message-broker/bullmq/broker.ts +141 -0
  41. package/src/core/message-broker/bullmq/index.ts +3 -0
  42. package/src/core/message-broker/bullmq/queue.ts +58 -0
  43. package/src/core/message-broker/bullmq/worker.ts +68 -0
  44. package/src/core/message-broker/kafka/basic-types.ts +45 -0
  45. package/src/core/message-broker/kafka/consumer.ts +95 -0
  46. package/src/core/message-broker/kafka/index.ts +3 -0
  47. package/src/core/message-broker/kafka/producer.ts +113 -0
  48. package/src/core/message-broker/rabitmq/basic-types.ts +44 -0
  49. package/src/core/message-broker/rabitmq/channel.ts +95 -0
  50. package/src/core/message-broker/rabitmq/consumer.ts +94 -0
  51. package/src/core/message-broker/rabitmq/index.ts +4 -0
  52. package/src/core/message-broker/rabitmq/producer.ts +100 -0
  53. package/src/core/message-broker/utilities.ts +50 -0
  54. package/src/core/middlewares/basic-types.ts +39 -0
  55. package/src/core/middlewares/decorators.ts +244 -0
  56. package/src/core/middlewares/index.ts +3 -0
  57. package/src/core/middlewares/middlewares.ts +246 -0
  58. package/src/core/parallel/index.ts +3 -0
  59. package/src/{utilities → core}/parallel/parallel.ts +11 -1
  60. package/src/core/rate-limit/basic-types.ts +43 -0
  61. package/src/core/rate-limit/index.ts +4 -0
  62. package/src/core/rate-limit/memory-store.ts +65 -0
  63. package/src/core/rate-limit/rate-limit.ts +134 -0
  64. package/src/core/rate-limit/redis-store.ts +141 -0
  65. package/src/core/retry/basic-types.ts +21 -0
  66. package/src/core/retry/decorator.ts +139 -0
  67. package/src/core/retry/index.ts +2 -0
  68. package/src/main.ts +6 -8
  69. package/src/routers/orders/index.router.ts +5 -1
  70. package/src/routers/orders/order.controller.ts +46 -59
  71. package/src/routers/products/index.router.ts +2 -1
  72. package/src/routers/products/product.controller.ts +25 -63
  73. package/src/routers/users/index.router.ts +1 -1
  74. package/src/routers/users/user.controller.ts +22 -50
  75. package/src/utilities/cache/memory-cache.ts +0 -74
  76. /package/src/{utilities → core}/logger/base-logger.interface.ts +0 -0
  77. /package/src/{utilities → core}/logger/logger.service.ts +0 -0
  78. /package/src/{utilities → core}/parallel/chanel.ts +0 -0
  79. /package/src/{utilities → core}/parallel/worker-pool.ts +0 -0
@@ -0,0 +1,259 @@
1
+ import {
2
+ SNSClient,
3
+ PublishCommand,
4
+ GetSMSAttributesCommand,
5
+ SetSMSAttributesCommand,
6
+ } from "@aws-sdk/client-sns";
7
+ import {
8
+ SESv2Client,
9
+ SendEmailCommand,
10
+ ListEmailIdentitiesCommand,
11
+ } from "@aws-sdk/client-sesv2";
12
+ import {
13
+ CreatePlatformApplicationCommand,
14
+ CreatePlatformApplicationCommandInput,
15
+ CreatePlatformEndpointCommand,
16
+ CreatePlatformEndpointCommandInput,
17
+ PublishCommand as SNSPublishCommand,
18
+ } from "@aws-sdk/client-sns";
19
+ import { EmailMessage, PushPlatformArns, SMSMessage } from "./basic-types";
20
+
21
+ /**
22
+ * Comprehensive TypeScript library for AWS SNS (SMS) and SES (Email) services.
23
+ * Provides simplified interfaces for sending messages with full TypeScript support.
24
+ *
25
+ * @example
26
+ * ```
27
+ * const messenger = new AWSMessenger("us-east-1");
28
+ * await messenger.sendSMS({ phoneNumber: '+1234567890', message: 'Hello!' });
29
+ * await messenger.sendEmail({
30
+ * to: ['user@example.com'],
31
+ * subject: 'Test',
32
+ * textBody: 'Hello from SES!',
33
+ * from: 'sender@verified.com'
34
+ * });
35
+ * ```
36
+ */
37
+ export class Messenger {
38
+ public sns!: SNSClient;
39
+ public ses!: SESv2Client;
40
+
41
+ /**
42
+ * Creates an instance of AWSMessenger for SNS and SES operations.
43
+ * @param options - Configuration options.
44
+ * @param [options.region=us-east-1] - AWS region for operations.
45
+ */
46
+ public constructor(region?: string, ...args: any[]) {
47
+ if (region != null) {
48
+ this.sns = new SNSClient({ region, ...args });
49
+ this.ses = new SESv2Client({ region, ...args });
50
+ }
51
+ }
52
+
53
+ /**
54
+ * Sends an SMS message via AWS SNS.
55
+ * @param message - SMS configuration object.
56
+ * @returns Promise resolving to SNS publish response with MessageId.
57
+ * @throws Throws if phone number is invalid, quota exceeded, or permissions missing.
58
+ */
59
+ public async sendSMS(message: SMSMessage): Promise<any> {
60
+ const command = new PublishCommand({
61
+ PhoneNumber: message.phoneNumber,
62
+ Message: message.message,
63
+ MessageAttributes: message.attributes,
64
+ });
65
+ return this.sns.send(command);
66
+ }
67
+
68
+ /**
69
+ * Sends a bulk SMS to multiple phone numbers.
70
+ * @param messages - Array of SMS configuration objects.
71
+ * @returns Promise resolving to array of publish responses.
72
+ */
73
+ public async sendBulkSMS(messages: SMSMessage[]): Promise<any[]> {
74
+ const results = [];
75
+ for (const msg of messages) {
76
+ results.push(await this.sendSMS(msg));
77
+ }
78
+ return results;
79
+ }
80
+
81
+ /**
82
+ * Gets current SMS attributes (delivery status, monthly spend, etc.).
83
+ * @returns Promise resolving to SMS attributes map.
84
+ */
85
+ public async getSMSAttributes(): Promise<any> {
86
+ const command = new GetSMSAttributesCommand({});
87
+ const response = await this.sns.send(command);
88
+ return response.attributes || {};
89
+ }
90
+
91
+ /**
92
+ * Sets SMS attributes like DefaultSMSType (Transactional/Promotional).
93
+ * @param attributes - Map of SMS attributes to set.
94
+ * @returns Promise resolving to updated attributes.
95
+ * @example
96
+ * await messenger.setSMSAttributes({ DefaultSMSType: 'Transactional' });
97
+ */
98
+ public async setSMSAttributes(attributes: Record<string, string>): Promise<any> {
99
+ const command = new SetSMSAttributesCommand({ attributes });
100
+ return this.sns.send(command);
101
+ }
102
+
103
+ /**
104
+ * Sends an email via AWS SES v2.
105
+ * @param message - Email configuration object.
106
+ * @returns Promise resolving to SES send response with MessageId.
107
+ * @throws Throws if sender not verified, quota exceeded, or invalid recipients.
108
+ */
109
+ public async sendEmail(message: EmailMessage): Promise<any> {
110
+ const command = new SendEmailCommand({
111
+ FromEmailAddress: message.from,
112
+ Destination: {
113
+ ToAddresses: message.to,
114
+ CcAddresses: message.cc,
115
+ BccAddresses: message.bcc,
116
+ },
117
+ Content: {
118
+ Simple: {
119
+ Subject: { Data: message.subject, Charset: "UTF-8" },
120
+ Body: {
121
+ Text: message.textBody
122
+ ? { Data: message.textBody, Charset: "UTF-8" }
123
+ : undefined,
124
+ Html: message.htmlBody
125
+ ? { Data: message.htmlBody, Charset: "UTF-8" }
126
+ : undefined,
127
+ },
128
+ },
129
+ },
130
+ ReplyToAddresses: message.replyTo,
131
+ });
132
+ return this.ses.send(command);
133
+ }
134
+
135
+ /**
136
+ * Lists all SES verified email addresses.
137
+ * @returns Promise resolving to array of verified email addresses.
138
+ */
139
+ public async listVerifiedEmails(): Promise<string[]> {
140
+ const command = new ListEmailIdentitiesCommand({});
141
+ const response = await this.ses.send(command);
142
+ // Filter identities for email addresses only (exclude domains)
143
+ const emails = (response.EmailIdentities || [])
144
+ .filter((identity) => identity.IdentityType === "EMAIL_ADDRESS")
145
+ .map((identity) => identity.IdentityName || "");
146
+ return emails;
147
+ }
148
+
149
+ /** Optional: cache or store platform application ARNs by platform */
150
+ public pushPlatformArns: PushPlatformArns | undefined;
151
+
152
+ /**
153
+ * Creates a platform application for push notifications.
154
+ * You can store the resulting PlatformApplicationArn to reuse for endpoints.
155
+ * @param platform - 'APNS', 'APNS_SANDBOX', 'GCM' (FCM), etc.
156
+ * @param attributes - Optional attributes for the platform app (e.g., PlatformCredential, PlatformPrincipal)
157
+ * @param name - Optional logical name for internal tracking (not strictly required)
158
+ * @returns Promise resolving to the created PlatformApplicationArn
159
+ */
160
+ public async createPlatformApplication(
161
+ platform: string,
162
+ attributes: Record<string, string>
163
+ ): Promise<string> {
164
+ const command = new CreatePlatformApplicationCommand({
165
+ Name: platform + "_application_" + Date.now(),
166
+ Platform: platform,
167
+ Attributes: attributes,
168
+ } as CreatePlatformApplicationCommandInput);
169
+ const response = await this.sns.send(command);
170
+ const arn = response.PlatformApplicationArn;
171
+ // Optionally cache by platform
172
+ if (!this.pushPlatformArns) this.pushPlatformArns = {};
173
+ this.pushPlatformArns[platform] = arn || "";
174
+ return arn || "";
175
+ }
176
+
177
+ /**
178
+ * Creates an endpoint for a device token under a given platform application.
179
+ * @param platform - platform key, e.g., 'APNS', 'APNS_SANDBOX', 'GCM'
180
+ * @param token - device push token (device token for APNS, registration token for FCM)
181
+ * @param customUserData - optional custom data to associate with the endpoint
182
+ * @returns Promise resolving to the EndpointArn
183
+ */
184
+ public async createPlatformEndpoint(
185
+ platform: string,
186
+ token: string,
187
+ customUserData?: string
188
+ ): Promise<string> {
189
+ const appArn = this.pushPlatformArns?.[platform];
190
+ if (!appArn) {
191
+ throw new Error(
192
+ `Platform application ARN for ${platform} not found. Create it with createPlatformApplication first.`
193
+ );
194
+ }
195
+ const command = new CreatePlatformEndpointCommand({
196
+ PlatformApplicationArn: appArn,
197
+ Token: token,
198
+ UserData: customUserData,
199
+ } as CreatePlatformEndpointCommandInput);
200
+ const response = await this.sns.send(command);
201
+ return response.EndpointArn || "";
202
+ }
203
+
204
+ /**
205
+ * Publish a push notification to a specific endpoint.
206
+ * The Message structure uses platform-specific payloads for APNS/FCM, etc.
207
+ * @param endpointArn - The SNS EndpointArn to target
208
+ * @param message - The generic message payload
209
+ * @param subject - Optional subject (for some platforms)
210
+ * @returns Promise resolving to Publish response with MessageId
211
+ */
212
+ public async publishPushToEndpoint(
213
+ endpointArn: string,
214
+ message: {
215
+ // Common fields; you can structure this as needed
216
+ title?: string;
217
+ body?: string;
218
+ // Optional Apple/Google-specific overrides
219
+ apns?: any; // APNS payload
220
+ android?: any; // Android payload
221
+ default?: string;
222
+ }
223
+ ): Promise<any> {
224
+ // Build a platform-specific JSON payload. AWS SNS expects a JSON object
225
+ // with keys per platform, as a string under the "Message" field,
226
+ // and a "MessageStructure": "json"
227
+
228
+ const payload: any = {};
229
+
230
+ if (message.apns) payload.APNS = JSON.stringify(message.apns);
231
+ if (message.android) payload.GCM = JSON.stringify(message.android);
232
+ if (message.default) payload.default = message.default;
233
+ // Fallback to a simple default if no platform-specific payload provided
234
+ if (!payload.APNS && !payload.GCM && !payload.default) {
235
+ const simple = {
236
+ aps: {
237
+ alert: {
238
+ title: message.title || "",
239
+ body: message.body || "",
240
+ },
241
+ },
242
+ };
243
+ payload.APNS = JSON.stringify(simple);
244
+ // Some implementations also set GCM
245
+ payload.GCM = JSON.stringify({
246
+ notification: { title: message.title, body: message.body },
247
+ });
248
+ }
249
+
250
+ const command = new SNSPublishCommand({
251
+ TargetArn: endpointArn,
252
+ MessageStructure: "json",
253
+ Message: JSON.stringify(payload),
254
+ });
255
+
256
+ // Use PublishCommand alias to avoid naming confusion
257
+ return this.sns.send(command);
258
+ }
259
+ }
@@ -0,0 +1,136 @@
1
+ import {
2
+ S3Client,
3
+ PutObjectCommand,
4
+ GetObjectCommand,
5
+ ListObjectsV2Command,
6
+ DeleteObjectCommand,
7
+ } from "@aws-sdk/client-s3";
8
+ import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
9
+ import { retry, RetryOptions } from "./utilities";
10
+ import { DownloadParams, UploadParams } from "./basic-types";
11
+ import fs from "fs";
12
+ import util from "util";
13
+
14
+ /**
15
+ * Client for interacting with AWS S3 buckets.
16
+ */
17
+ export class S3 {
18
+ private client!: S3Client;
19
+
20
+ /**
21
+ * Constructor for S3Client.
22
+ * @param {string} [region] - The AWS region to use for the client.
23
+ * If not provided, the default region will be used.
24
+ */
25
+ public constructor(region?: string) {
26
+ if (region != null) {
27
+ this.client = new S3Client({ region });
28
+ }
29
+ }
30
+
31
+ /**
32
+ * Uploads a file to an S3 bucket.
33
+ * @param {UploadParams} params - The upload parameters.
34
+ * @param {string} params.bucket - The name of the S3 bucket to upload to.
35
+ * @param {string} params.key - The key of the object to create in the bucket.
36
+ * @param {string} params.filePath - The local path to the file to upload.
37
+ * @param {Record<string, any>} [params.extraArgs] - Additional parameters to pass to the PutObjectCommand.
38
+ * @returns {Promise<void>} A promise resolving when the upload is complete.
39
+ */
40
+ public async upload(
41
+ params: UploadParams,
42
+ retryOptions?: RetryOptions
43
+ ): Promise<void> {
44
+ const { bucket, key, filePath, extraArgs } = params;
45
+ const readFile = util.promisify(fs.promises.readFile);
46
+
47
+ const body = await readFile(filePath, undefined);
48
+
49
+ await retry(async () => {
50
+ await this.client.send(
51
+ new PutObjectCommand({
52
+ Bucket: bucket,
53
+ Key: key,
54
+ Body: body as any,
55
+ ...extraArgs,
56
+ })
57
+ );
58
+ }, retryOptions);
59
+ }
60
+
61
+ /**
62
+ * Downloads an object from an S3 bucket.
63
+ * @param {DownloadParams} params - The download parameters.
64
+ * @param {string} params.bucket - The name of the S3 bucket to download from.
65
+ * @param {string} params.key - The key of the object to download.
66
+ * @param {string} params.destPath - The local path to write the downloaded file to.
67
+ * @returns {Promise<void>} A promise resolving when the download is complete.
68
+ */
69
+ public async download(params: DownloadParams): Promise<void> {
70
+ const { bucket, key, destPath } = params;
71
+ const resp = await this.client.send(
72
+ new GetObjectCommand({ Bucket: bucket, Key: key })
73
+ );
74
+
75
+ const writeStream = fs.createWriteStream(destPath);
76
+
77
+ // resp.Body is a stream
78
+ if (resp.Body && "pipe" in resp.Body) {
79
+ await new Promise<void>((resolve, reject) => {
80
+ (resp.Body as any).pipe(writeStream);
81
+ writeStream.on("finish", () => resolve());
82
+ writeStream.on("error", (e) => reject(e));
83
+ });
84
+ } else {
85
+ // Fallback if Body is not streamable
86
+ const chunks: Buffer[] = [];
87
+ for await (const chunk of resp.Body as any)
88
+ chunks.push(Buffer.from(chunk));
89
+ await fs.promises.writeFile(destPath, Buffer.concat(chunks));
90
+ }
91
+ }
92
+
93
+ /**
94
+ * Lists the objects in an S3 bucket, optionally filtered by a prefix.
95
+ * @param {string} bucket - The name of the S3 bucket to list objects from.
96
+ * @param {string} [prefix] - The optional prefix to filter objects by.
97
+ * @returns {Promise<string[]>} A promise resolving with an array of object keys.
98
+ */
99
+ public async listObjects(bucket: string, prefix?: string): Promise<string[]> {
100
+ const cmd = new ListObjectsV2Command({ Bucket: bucket, Prefix: prefix });
101
+ const resp: any = await this.client.send(cmd);
102
+ const keys = (resp.Contents || []).map((c: any) => c.Key).filter(Boolean);
103
+ return keys;
104
+ }
105
+
106
+ /**
107
+ * Deletes an object from an S3 bucket.
108
+ * @param {string} bucket - The name of the S3 bucket to delete from.
109
+ * @param {string} key - The key of the object to delete.
110
+ * @returns {Promise<void>} A promise resolving when the delete is complete.
111
+ */
112
+ public async deleteObject(bucket: string, key: string): Promise<void> {
113
+ await this.client.send(
114
+ new DeleteObjectCommand({ Bucket: bucket, Key: key })
115
+ );
116
+ }
117
+
118
+ /**
119
+ * Generates a presigned URL for downloading an object from an S3 bucket.
120
+ * @param bucket The name of the S3 bucket to download from.
121
+ * @param key The key of the object to download.
122
+ * @param expirationInSeconds The number of seconds until the presigned URL expires.
123
+ * Defaults to 3600 (1 hour).
124
+ * @returns A promise resolving to the presigned URL.
125
+ */
126
+ public async presignedUrl(
127
+ bucket: string,
128
+ key: string,
129
+ expirationInSeconds = 3600
130
+ ): Promise<string> {
131
+ const cmd = new GetObjectCommand({ Bucket: bucket, Key: key });
132
+ return await getSignedUrl(this.client, cmd, {
133
+ expiresIn: expirationInSeconds,
134
+ });
135
+ }
136
+ }
@@ -0,0 +1,44 @@
1
+ export type RetryOptions = {
2
+ retries?: number;
3
+ baseDelayMs?: number;
4
+ maxDelayMs?: number;
5
+ jitter?: boolean;
6
+ };
7
+
8
+ export function sleep(ms: number): Promise<void> {
9
+ return new Promise((resolve) => setTimeout(resolve, ms));
10
+ }
11
+
12
+ /**
13
+ * Simple exponential backoff retry wrapper.
14
+ * Retries the provided async function according to options.
15
+ */
16
+ export async function retry<T>(
17
+ fn: () => Promise<T>,
18
+ options: RetryOptions = {}
19
+ ): Promise<T> {
20
+ const {
21
+ retries = 1,
22
+ baseDelayMs = 200,
23
+ maxDelayMs = 2000,
24
+ jitter = true,
25
+ } = options;
26
+
27
+ let attempt = 0;
28
+ while (true) {
29
+ try {
30
+ return await fn();
31
+ } catch (err) {
32
+ attempt++;
33
+ if (attempt > retries) {
34
+ throw err;
35
+ }
36
+ let delay = Math.min(maxDelayMs, baseDelayMs * Math.pow(2, attempt - 1));
37
+ if (jitter) {
38
+ const jitterMs = Math.random() * 100;
39
+ delay = Math.max(0, delay + jitterMs);
40
+ }
41
+ await sleep(delay);
42
+ }
43
+ }
44
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * A simple cache interface.
3
+ */
4
+ export interface AsyncCache<Entity> {
5
+ set: (key: string, value: Entity) => Promise<void>;
6
+ get: (key: string) => Promise<Entity | null>;
7
+ delete: (key: string) => Promise<void>;
8
+ has: (key: string) => Promise<boolean>;
9
+ clear: () => Promise<void>;
10
+ }
11
+
12
+ /**
13
+ * A function that takes a variable number of arguments and returns a string that uniquely identifies them.
14
+ */
15
+ export type KeyResolver = <MethodArgs extends any[] = any[]>(
16
+ ...args: MethodArgs
17
+ ) => string;
@@ -0,0 +1,72 @@
1
+ import { AsyncCache, KeyResolver } from "./basic-types";
2
+ import { createMethodDecorator } from "@/core/helpers";
3
+
4
+ /**
5
+ * Default key resolver for memoization.
6
+ * Takes a variable number of arguments and returns a string that uniquely identifies them.
7
+ * For objects, it uses JSON.stringify to convert the object to a string.
8
+ * For other types, it uses the primitive value of the argument.
9
+ * The resulting strings are joined with "|" to form the final key.
10
+ * @param {...args} MethodArgs
11
+ * @returns {string}
12
+ */
13
+ function defaultKeyResolver<MethodArgs extends any[] = any[]>(
14
+ ...args: MethodArgs
15
+ ): string {
16
+ return args.length === 0
17
+ ? "key"
18
+ : args
19
+ .map((a) => (typeof a === "object" ? JSON.stringify(a) : a))
20
+ .join("|");
21
+ }
22
+
23
+ /**
24
+ * Decorator that memoizes a method, using a cache of type AsyncCache<Entity>
25
+ * and a key resolver of type KeyResolver.
26
+ * @returns {MethodDecorator}
27
+ */
28
+ export function Memoize<Entity, MethodArgs extends any[] = any[]>(
29
+ cache: AsyncCache<Entity>,
30
+ keyResolver?: KeyResolver
31
+ ) {
32
+ return createMethodDecorator<
33
+ [AsyncCache<Entity>, KeyResolver | undefined],
34
+ MethodArgs
35
+ >(async ([cache, keyResolver], methodArgs, method, self) => {
36
+ keyResolver = keyResolver ?? defaultKeyResolver;
37
+ const key = keyResolver(...methodArgs);
38
+ const has = await cache?.has(key);
39
+ if (has) {
40
+ const value = await cache?.get(key);
41
+ return value;
42
+ } else {
43
+ const value = await method.apply(self, methodArgs);
44
+ await cache?.set(key, value as Entity);
45
+ return value;
46
+ }
47
+ })(cache, keyResolver);
48
+ }
49
+
50
+ /**
51
+ * Decorator that invalidates a cache of type AsyncCache<Entity> by key.
52
+ * @returns {MethodDecorator}
53
+ */
54
+ export function Invalidate<Entity>(
55
+ cache: AsyncCache<Entity>,
56
+ shouldClearAll: boolean = true
57
+ ) {
58
+ return createMethodDecorator<
59
+ [AsyncCache<Entity>, boolean | true],
60
+ [string | undefined]
61
+ >(undefined, async (_value, [cache, shouldClearAll], [key]) => {
62
+ if (shouldClearAll === true) {
63
+ await cache.clear();
64
+ } else if (typeof key === "string" || typeof key === "number") {
65
+ await cache.delete(key);
66
+ } else {
67
+ throw new Error("The key must be string or number.");
68
+ }
69
+
70
+ return null;
71
+ })(cache, shouldClearAll);
72
+ }
@@ -0,0 +1,4 @@
1
+ export * from "./basic-types";
2
+ export * from "./memory-cache.class";
3
+ export * from "./redis-cache.class";
4
+ export * from "./decorator";
@@ -0,0 +1,119 @@
1
+ import { AsyncCache } from "./basic-types";
2
+
3
+ /**
4
+ * A simple in-memory map-based cache implementing AsyncCache<T>.
5
+ * This is a straightforward wrapper around a Map<string, T>.
6
+ */
7
+ export class MapAsyncCache<T> implements AsyncCache<T> {
8
+ private startTimeMs?: number;
9
+
10
+ public constructor(
11
+ private capacity?: number,
12
+ private durationMs?: number,
13
+ private cache?: Map<string, T>
14
+ ) {
15
+ this.startTimeMs = this.startTimeMs ?? Date.now();
16
+ this.durationMs = this.durationMs ?? Number.POSITIVE_INFINITY;
17
+ this.capacity = this.capacity ?? Number.POSITIVE_INFINITY;
18
+ this.cache = this.cache ?? new Map<string, T>();
19
+ }
20
+
21
+ public async size(): Promise<number> {
22
+ return await this.cache!.size;
23
+ }
24
+
25
+ public async keys(): Promise<string[]> {
26
+ return await Array.fromAsync(this.cache?.keys() ?? []);
27
+ }
28
+
29
+ public async values(): Promise<T[]> {
30
+ return await Array.fromAsync(this.cache?.values() ?? []);
31
+ }
32
+
33
+ public async entries(): Promise<[string, T][]> {
34
+ return await Array.fromAsync(this.cache?.entries() ?? []);
35
+ }
36
+
37
+ /**
38
+ * Whether the cache is still valid.
39
+ * This is a computed property that returns true if the cache
40
+ * has not yet expired, and false otherwise.
41
+ * @returns true if the cache is still valid, false otherwise
42
+ */
43
+ public get isValid(): boolean {
44
+ return this.startTimeMs! + this.durationMs! > Date.now();
45
+ }
46
+
47
+ /**
48
+ * Reset the cache if it has expired.
49
+ * @returns true if the cache was reset, false otherwise
50
+ */
51
+ public async resetIfExpired(): Promise<boolean> {
52
+ if (this.isValid == false) {
53
+ await this.cache?.clear();
54
+ this.startTimeMs = Date.now();
55
+ return true;
56
+ }
57
+ return false;
58
+ }
59
+
60
+ /**
61
+ * Asynchronously set a value by key.
62
+ * @param key The cache key
63
+ * @param value The value to cache
64
+ */
65
+ public async set(key: string, value: T): Promise<void> {
66
+ if ((await this.resetIfExpired()) == true) return;
67
+ if (value != null) {
68
+ if (this.cache?.has(key)) {
69
+ await this.cache?.set(key, value);
70
+ } else {
71
+ if (
72
+ (this.capacity ?? Number.POSITIVE_INFINITY) > (this.cache?.size ?? 0)
73
+ ) {
74
+ await this.cache?.set(key, value);
75
+ }
76
+ }
77
+ } else {
78
+ await this.cache?.delete(key);
79
+ }
80
+ }
81
+
82
+ /**
83
+ * Asynchronously get a value by key.
84
+ *
85
+ * @param key The cache key
86
+ * - If the key exists, returns the value.
87
+ * - If the key does not exist, returns null.
88
+ */
89
+ public async get(key: string): Promise<T | null> {
90
+ if (await this.cache?.has(key)) {
91
+ return (await this.cache?.get(key)) ?? null;
92
+ }
93
+
94
+ return null;
95
+ }
96
+
97
+ /**
98
+ * Asynchronously delete a value by key.
99
+ */
100
+ public async delete(key: string): Promise<void> {
101
+ if ((await this.resetIfExpired()) == true) return;
102
+ await this.cache?.delete(key);
103
+ }
104
+
105
+ /**
106
+ * Asynchronously check if a key exists in the cache.
107
+ */
108
+ public async has(key: string): Promise<boolean> {
109
+ if ((await this.resetIfExpired()) == true) return false;
110
+ return (await this.cache?.has(key)) ?? false;
111
+ }
112
+
113
+ /**
114
+ * Asynchronously clear the cache.
115
+ */
116
+ public async clear(): Promise<void> {
117
+ await this.cache?.clear();
118
+ }
119
+ }