codeweaver 3.1.3 → 4.0.1

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 +54 -64
  71. package/src/routers/products/index.router.ts +2 -1
  72. package/src/routers/products/product.controller.ts +33 -68
  73. package/src/routers/users/index.router.ts +1 -1
  74. package/src/routers/users/user.controller.ts +25 -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,316 @@
1
+ import "reflect-metadata";
2
+ import { AsyncFn } from "./types";
3
+
4
+ /**
5
+ * Factory for creating a method decorator with optional pre- and post-execution hooks.
6
+ *
7
+ * @template DecoratorArgs, MethodArgs
8
+ *
9
+ * @param before
10
+ * Optional hook invoked before the original method runs.
11
+ * - Parameters: (decoratorArgs, methodArgs, target, propertyKey, descriptor)
12
+ * - decoratorArgs: The arguments passed to the decorator.
13
+ * - methodArgs: The arguments passed to the decorated method.
14
+ * - target: The prototype of the class for instance methods.
15
+ * - propertyKey: The name of the method being decorated.
16
+ * - descriptor: The method's PropertyDescriptor.
17
+ * - Returns: Promise<boolean> indicating whether the original method should execute.
18
+ * - If it resolves to false, the original method will not run.
19
+ *
20
+ * @param after
21
+ * Optional hook invoked after the original method completes.
22
+ * - Parameters: (methodResult, decoratorArgs, methodArgs, target, propertyKey, descriptor)
23
+ * - methodResult: The value returned by the original method (or null if it didn't run).
24
+ * - decoratorArgs: The arguments passed to the decorator.
25
+ * - methodArgs: The arguments passed to the decorated method.
26
+ * - target: The prototype of the class for instance methods.
27
+ * - propertyKey: The name of the method being decorated.
28
+ * - descriptor: The method's PropertyDescriptor.
29
+ * - Returns: Promise<void>.
30
+ *
31
+ * @returns A decorator factory which, when invoked with decoratorArgs, yields a MethodDecorator.
32
+ *
33
+ * Notes:
34
+ * - Be explicit about what the hooks receive and what they control.
35
+ * - Clarify return values and behavior when before returns false.
36
+ * - Keep terminology consistent with your codebase (hook vs. decorator vs. wrapper).
37
+ * - If you rely on Reflect metadata, you can store metadata inside the hooks.
38
+ *
39
+ * Example usage:
40
+ *
41
+ * const Decorator = createMethodDecorator<[string], [number]>(
42
+ * async (decoratorArgs, methodArgs, target, propertyKey, descriptor) => { ... }, // before
43
+ * async (result, decoratorArgs, methodArgs, target, propertyKey, descriptor) => { ... } // after
44
+ * );
45
+ */
46
+ export function createMethodDecorator<
47
+ DecoratorArgs extends any[] = any[],
48
+ MethodArgs extends any[] = any[]
49
+ >(
50
+ before?: (
51
+ decoratorArgs: DecoratorArgs,
52
+ methodArgs: MethodArgs,
53
+ method: AsyncFn,
54
+ rawMethodArgs: any[],
55
+ classInstance: any,
56
+ target: Object,
57
+ propertyKey: string | symbol,
58
+ descriptor: PropertyDescriptor
59
+ ) => Promise<any>,
60
+ after?: (
61
+ methodResult: any,
62
+ decoratorArgs: DecoratorArgs,
63
+ methodArgs: MethodArgs,
64
+ method: AsyncFn,
65
+ rawMethodArgs: any[],
66
+ classInstance: any,
67
+ target: Object,
68
+ propertyKey: string | symbol,
69
+ descriptor: PropertyDescriptor
70
+ ) => Promise<any>
71
+ ): (...decoratorArgs: DecoratorArgs) => MethodDecorator {
72
+ return (...decoratorArgs: DecoratorArgs) => {
73
+ return (
74
+ target: Object,
75
+ propertyKey: string | symbol,
76
+ descriptor: PropertyDescriptor
77
+ ) => {
78
+ const originalMethod = descriptor.value as (
79
+ ...args: any[]
80
+ ) => Promise<any>;
81
+
82
+ // If there is no function to wrap, bail out gracefully
83
+ if (typeof originalMethod !== "function") {
84
+ throw new Error("Cannot wrap non-function.");
85
+ }
86
+
87
+ // Wrap the original method to insert before/after hooks
88
+ descriptor.value = async function (...methodArgs: any[]) {
89
+ // Run the optional before hook. If not provided, default to allowing execution.
90
+ const beforeCallbackResult =
91
+ before != null
92
+ ? await before(
93
+ decoratorArgs,
94
+ methodArgs as MethodArgs,
95
+ originalMethod,
96
+ methodArgs,
97
+ this,
98
+ target,
99
+ propertyKey,
100
+ descriptor
101
+ )
102
+ : null;
103
+
104
+ // Execute the original method only if allowed
105
+ const methodResult =
106
+ beforeCallbackResult === null
107
+ ? await originalMethod.apply(this, methodArgs)
108
+ : beforeCallbackResult;
109
+
110
+ // Run the optional after hook with the obtained result
111
+ const afterCallbackResult =
112
+ after != null
113
+ ? await after(
114
+ methodResult,
115
+ decoratorArgs,
116
+ methodArgs as MethodArgs,
117
+ originalMethod,
118
+ methodArgs,
119
+ this,
120
+ target,
121
+ propertyKey,
122
+ descriptor
123
+ )
124
+ : methodResult;
125
+
126
+ // Return the original result (even if before denied, it's null per above)
127
+ return afterCallbackResult === null
128
+ ? methodResult
129
+ : afterCallbackResult;
130
+ };
131
+ };
132
+ };
133
+ }
134
+
135
+ type ParamDecoratorFactory<DecoratorArgs extends any[] = any[]> = (
136
+ ...args: DecoratorArgs
137
+ ) => ParameterDecorator;
138
+
139
+ /**
140
+ * Factory to create a parameter decorator with optional runtime logic.
141
+ *
142
+ * The returned decorator can be used to attach per-parameter behavior or metadata
143
+ * to a method parameter. The `handler` is invoked when the decorator runs, giving
144
+ * you access to the decorator arguments, the resolved parameter type (if available),
145
+ * the index of the decorated parameter, the target (prototype) and the method name.
146
+ *
147
+ * Notes:
148
+ * - propertyKey can be string or symbol (or undefined in edge cases).
149
+ * - If you rely on Reflect metadata, you can store metadata inside the handler.
150
+ */
151
+ export function createParamDecorator<
152
+ DecoratorArgs extends any[] = any[],
153
+ MethodArgs extends any[] = any[],
154
+ ParamType = any
155
+ >(
156
+ handler: (
157
+ decoratorArgs: DecoratorArgs,
158
+ methodArgs: MethodArgs | undefined,
159
+ parameter: ParamType | undefined,
160
+ parameterIndex: number,
161
+ target: Object,
162
+ propertyKey?: string | symbol
163
+ ) => void | Promise<void>
164
+ ): ParamDecoratorFactory<DecoratorArgs> {
165
+ // Return a factory that captures the decoratorArgs and returns a real ParameterDecorator
166
+ return (...decoratorArgs: DecoratorArgs) => {
167
+ return (
168
+ target: Object,
169
+ propertyKey: string | symbol | undefined,
170
+ parameterIndex: number
171
+ ) => {
172
+ // Resolve the parameter type if available (design:paramtypes)
173
+ let parameter: ParamType | undefined = undefined;
174
+ let methodArgs: MethodArgs | undefined = undefined;
175
+ if (typeof propertyKey !== "undefined" && target != null) {
176
+ try {
177
+ const types = Reflect.getMetadata(
178
+ "design:paramtypes",
179
+ target,
180
+ propertyKey
181
+ );
182
+ if (Array.isArray(types) && typeof parameterIndex === "number") {
183
+ methodArgs = types as MethodArgs;
184
+ parameter = types[parameterIndex] as ParamType;
185
+ }
186
+ } catch {
187
+ // metadata not available; swallow
188
+ }
189
+ }
190
+
191
+ // Run user-supplied handler
192
+ // Note: allow handler to be sync or async
193
+ const maybePromise = handler(
194
+ decoratorArgs,
195
+ methodArgs,
196
+ parameter,
197
+ parameterIndex,
198
+ target,
199
+ propertyKey
200
+ );
201
+
202
+ void maybePromise;
203
+ };
204
+ };
205
+ }
206
+
207
+ // Hook results
208
+ type BeforeGetResult = boolean | Promise<boolean>;
209
+ type AfterGetResult = void | Promise<void>;
210
+ type BeforeSetResult = boolean | Promise<boolean>;
211
+ type AfterSetResult = void | Promise<void>;
212
+
213
+ // Common argument lists
214
+ type GetArgs<DecoratorArgs extends any[] = any[]> = {
215
+ decoratorArgs: DecoratorArgs;
216
+ target: Object;
217
+ propertyKey: string | symbol;
218
+ // current value (if you want to know what the value was before reading)
219
+ currentValue?: any;
220
+ };
221
+
222
+ type SetArgs<DecoratorArgs extends any[] = any[]> = {
223
+ decoratorArgs: DecoratorArgs;
224
+ target: Object;
225
+ propertyKey: string | symbol;
226
+ newValue: any;
227
+ // current value (before set)
228
+ currentValue?: any;
229
+ };
230
+
231
+ /**
232
+ * Factory for creating a property decorator with separate hooks for
233
+ * get and set operations. Each hook is optional and can be composed
234
+ * independently.
235
+ */
236
+ export function createPropertyDecorator<
237
+ DecoratorArgs extends any[] = any[],
238
+ // Value type can be inferred from usage; here we keep it generic
239
+ Value = any
240
+ >(options?: {
241
+ beforeGet?: (ctx: GetArgs<DecoratorArgs>) => BeforeGetResult;
242
+ afterGet?: (ctx: GetArgs<DecoratorArgs> & { value: Value }) => AfterGetResult;
243
+ beforeSet?: (ctx: SetArgs<DecoratorArgs>) => BeforeSetResult;
244
+ afterSet?: (ctx: SetArgs<DecoratorArgs> & { value: Value }) => AfterSetResult;
245
+ }): (...decoratorArgs: DecoratorArgs) => PropertyDecorator {
246
+ return (...decoratorArgs: DecoratorArgs) => {
247
+ return (target: Object, propertyKey: string | symbol) => {
248
+ // Backing field to store the value
249
+ const backingKey = Symbol(`__${String(propertyKey)}`);
250
+
251
+ // Retrieve existing descriptor if any
252
+ const descriptor = Object.getOwnPropertyDescriptor(
253
+ target,
254
+ propertyKey
255
+ ) || {
256
+ configurable: true,
257
+ enumerable: true,
258
+ };
259
+
260
+ Object.defineProperty(target, propertyKey, {
261
+ configurable: descriptor.configurable ?? true,
262
+ enumerable: descriptor.enumerable ?? true,
263
+ get: function (): Value {
264
+ const currentValue = this[backingKey];
265
+ const ctx: GetArgs<DecoratorArgs> = {
266
+ decoratorArgs,
267
+ target,
268
+ propertyKey,
269
+ currentValue,
270
+ };
271
+
272
+ const run = async () => {
273
+ const allowRead = options?.beforeGet
274
+ ? await options.beforeGet(ctx)
275
+ : true;
276
+ const value = allowRead ? currentValue : currentValue;
277
+ if (options?.afterGet) {
278
+ await options.afterGet({ ...ctx, value } as any);
279
+ }
280
+ return value as Value;
281
+ };
282
+
283
+ // Return a promise if any hook is async
284
+ // Otherwise, return synchronously
285
+ // Here we opt to return a Promise to keep behavior consistent with async hooks
286
+ return run() as unknown as Value;
287
+ },
288
+ set: function (newValue: any) {
289
+ const currentValue = this[propertyKey as string];
290
+ const ctx: SetArgs<DecoratorArgs> = {
291
+ decoratorArgs,
292
+ target,
293
+ propertyKey,
294
+ newValue,
295
+ currentValue,
296
+ };
297
+
298
+ const run = async () => {
299
+ const allowWrite = options?.beforeSet
300
+ ? await options.beforeSet(ctx)
301
+ : true;
302
+ if (allowWrite) {
303
+ this[backingKey] = newValue;
304
+ }
305
+ if (options?.afterSet) {
306
+ await options.afterSet({ ...ctx, value: newValue } as any);
307
+ }
308
+ };
309
+
310
+ // Fire asynchronously to mirror async hooks
311
+ void run();
312
+ },
313
+ });
314
+ };
315
+ };
316
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Formats a string by replacing placeholders with provided arguments.
3
+ * Placeholders are in the format {index}, where index is the zero-based position of the argument.
4
+ */
5
+ export function format(template: string, ...args: any[]): string {
6
+ return template.replace(/{(\d+)}/g, (match, index) => {
7
+ return args[index] !== undefined ? args[index] : match;
8
+ });
9
+ }
@@ -0,0 +1,7 @@
1
+ export * from "./assignment";
2
+ export * from "./conversion";
3
+ export * from "./comparison";
4
+ export * from "./range";
5
+ export * from "./types";
6
+ export * from "./format";
7
+ export * from "./decorators";
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Generate a range of numbers.
3
+ *
4
+ * Creates an array of numbers starting at `start`, up to (and optionally including)
5
+ * `end`, incrementing by `step`. The function supports both positive and negative steps.
6
+ *
7
+ * @param start - The starting value of the range (inclusive by default).
8
+ * @param end - The end value of the range. The range will stop before this value unless you enable `inclusiveEnd`.
9
+ * @param step - The difference between successive numbers. Must be non-zero. Defaults to 1.
10
+ * @param inclusiveEnd - If true, includes the `end` value in the result (when stepping towards it). Defaults to false.
11
+ * @returns An array of numbers representing the generated range.
12
+ *
13
+ * @throws If `step` is 0.
14
+ *
15
+ * @example
16
+ * [1, 2, 3, 4, 5]
17
+ * range(1, 6);
18
+ *
19
+ * @example
20
+ * [1, 3, 5]
21
+ * range(1, 6, 2);
22
+ *
23
+ * @example
24
+ * [1, 2, 3, 4, 5] (inclusive end)
25
+ * range(1, 5, 1, true);
26
+ *
27
+ * @example
28
+ * [5, 3, 1] (negative step, exclusive end)
29
+ * range(5, 0, -2);
30
+ *
31
+ * @example
32
+ * [5, 3, 1, -1] (negative step, inclusive end)
33
+ * range(5, -2, -2, true);
34
+ */
35
+ export function range(
36
+ start: number,
37
+ end: number,
38
+ step = 1,
39
+ inclusiveEnd: boolean = false
40
+ ): number[] {
41
+ if (step === 0) throw new Error("step must be non-zero");
42
+ // Helper to build an array from a function over indices
43
+ // For inclusiveEnd = false:
44
+ // if step > 0 -> [start, start+step, ..., < end]
45
+ // if step < 0 -> [start, start+step, ..., > end]
46
+ // We'll compute count and then map over [0..count-1]
47
+ if (inclusiveEnd === false) {
48
+ if (step > 0) {
49
+ const count = Math.max(0, Math.ceil((end - start) / step));
50
+ return Array.from({ length: count }, (_, i) => start + i * step);
51
+ } else {
52
+ const count = Math.max(0, Math.ceil((start - end) / -step));
53
+ return Array.from({ length: count }, (_, i) => start + i * step);
54
+ }
55
+ }
56
+
57
+ // inclusiveEnd = true
58
+ if (step > 0) {
59
+ // include end if reachable: values are start, start+step, ..., end
60
+ const count = start <= end ? Math.floor((end - start) / step) + 1 : 0;
61
+ return Array.from({ length: count }, (_, i) => start + i * step);
62
+ } else {
63
+ // step < 0
64
+ const count = start >= end ? Math.floor((start - end) / -step) + 1 : 0;
65
+ return Array.from({ length: count }, (_, i) => start + i * step);
66
+ }
67
+ }
@@ -0,0 +1,3 @@
1
+ export type SyncFn = (...args: any[]) => any;
2
+ export type AsyncFn = (...args: any[]) => Promise<any>;
3
+ export type Fn = (...args: any[]) => Promise<any> | any;
@@ -0,0 +1,4 @@
1
+ import { resolve } from "@/core/container";
2
+ import { WinstonLoggerService } from "./winston-logger.service";
3
+
4
+ export const logger = resolve(WinstonLoggerService);
@@ -85,7 +85,7 @@ const customLevels = {
85
85
 
86
86
  winston.addColors(customLevels.colors);
87
87
 
88
- export const logger = winston.createLogger({
88
+ export const winstonLogger = winston.createLogger({
89
89
  levels: customLevels.levels,
90
90
  level: "trace",
91
91
  transports: [consoleTransport, fileTransport],
@@ -1,5 +1,5 @@
1
- import { logger } from "./logger.config";
2
- import { Injectable } from "../container";
1
+ import { winstonLogger } from "./winston-logger.config";
2
+ import { Injectable } from "../container/container";
3
3
  import { LoggerService } from "./logger.service";
4
4
 
5
5
  @Injectable({ singleton: true })
@@ -11,6 +11,6 @@ export class WinstonLoggerService extends LoggerService {
11
11
  * Constructs a new LoggerService instance.
12
12
  */
13
13
  public constructor() {
14
- super(logger);
14
+ super(winstonLogger);
15
15
  }
16
16
  }
@@ -0,0 +1,67 @@
1
+ import { JobsOptions } from "bullmq";
2
+ import { BrokerSerializer } from "../utilities";
3
+
4
+ /**
5
+ * Configuration options for a BullMQ queue.
6
+ */
7
+ export interface QueueConfig {
8
+ name: string;
9
+ connection?: any; // redis options or a BullMQ connection object
10
+ defaultJobOptions?: JobsOptions;
11
+ limiter?: { max: number; duration: number } | undefined;
12
+ prefix?: string;
13
+ }
14
+
15
+ /**
16
+ * Configuration options for a BullMQ producer.
17
+ */
18
+ export interface ProducerConfig<T = any> {
19
+ queueName: string;
20
+ connection?: any;
21
+ defaultJobOptions?: JobsOptions;
22
+ serializer?: BrokerSerializer<T>;
23
+ retry?: { attempts?: number; backoffMs?: number; maxMs?: number };
24
+ }
25
+
26
+ /**
27
+ * Configuration options for a BullMQ message.
28
+ */
29
+ export interface BrokerMessage<T = any> {
30
+ topic: string;
31
+ payload: T;
32
+ headers?: any;
33
+ timestamp: number;
34
+ id?: string;
35
+ }
36
+
37
+ /**
38
+ * Configuration options for a BullMQ consumer.
39
+ */
40
+ export interface ConsumerConfig<T = any> {
41
+ queueName: string;
42
+ connection?: any;
43
+ concurrency?: number;
44
+ processor: (job: {
45
+ id: string;
46
+ data: T;
47
+ attemptsMade: number;
48
+ timestamp: number;
49
+ }) => Promise<void> | void;
50
+ serializer?: BrokerSerializer<T>;
51
+ }
52
+
53
+ /**
54
+ * Configuration options for a BullMQ broker.
55
+ */
56
+ export interface Broker {
57
+ publish<T>(topic: string, message: T, options?: any): Promise<void>;
58
+ subscribe<T>(
59
+ topic: string,
60
+ handler: (msg: BrokerMessage<T>) => Promise<void> | void,
61
+ options?: any
62
+ ): Promise<void>;
63
+ unsubscribe<T>(
64
+ topic: string,
65
+ handler: (msg: BrokerMessage<T>) => Promise<void> | void
66
+ ): Promise<void>;
67
+ }
@@ -0,0 +1,141 @@
1
+ import { Queue, JobScheduler, Worker } from "bullmq";
2
+ import { Broker, BrokerMessage } from "./basic-types";
3
+
4
+ /**
5
+ * Topic handler function type.
6
+ */
7
+ type TopicHandler<T = any> = (msg: BrokerMessage<T>) => Promise<void> | void;
8
+
9
+ /**
10
+ * BullMQ message broker implementation.
11
+ */
12
+ export class BullMqBroker implements Broker {
13
+ private queues = new Map<string, Queue>();
14
+ private workers = new Map<string, Worker>();
15
+ private topicHandlers = new Map<string, Set<TopicHandler<any>>>();
16
+
17
+ /**
18
+ * Constructor for a BullMQ message broker.
19
+ * @param options - Optional configuration options for the broker.
20
+ * If provided, the connection option will be used when creating queues.
21
+ */
22
+ constructor(private connection?: any) {}
23
+
24
+ /**
25
+ * Returns a BullMQ queue for a given topic.
26
+ * If the queue does not exist, it will be created with the given connection options.
27
+ * @param topic - The topic to get the queue for.
28
+ * @returns The BullMQ queue for the given topic.
29
+ */
30
+ private getQueueForTopic(topic: string): Queue {
31
+ const key = `topic:${topic}`;
32
+ if (!this.queues.has(key)) {
33
+ const q = new Queue(key, {
34
+ connection: this.connection,
35
+ });
36
+ this.queues.set(key, q);
37
+ }
38
+ // cast safe as Queue
39
+ return this.queues.get(key)!;
40
+ }
41
+
42
+ /**
43
+ * Returns a worker for a given topic.
44
+ * If the worker does not exist, it will be created with the given connection options.
45
+ * The worker will be responsible for processing all messages published to the given topic.
46
+ * @param topic - The topic to get the worker for.
47
+ * @returns The worker for the given topic.
48
+ */
49
+ private getWorkerForTopic(topic: string): Worker {
50
+ const key = `topic:${topic}`;
51
+ if (!this.workers.has(key)) {
52
+ const worker = new Worker(
53
+ key,
54
+ async (job: any) => {
55
+ const payload = job.data;
56
+ const msg: BrokerMessage = {
57
+ topic,
58
+ payload,
59
+ timestamp: Date.now(),
60
+ id: job.id,
61
+ };
62
+ const handlers = this.topicHandlers.get(topic);
63
+ if (handlers && handlers.size > 0) {
64
+ for (const h of Array.from(handlers)) {
65
+ await h(msg);
66
+ }
67
+ }
68
+ },
69
+ { connection: this.connection }
70
+ );
71
+ this.workers.set(key, worker);
72
+ }
73
+ return this.workers.get(key)!;
74
+ }
75
+
76
+ /**
77
+ * Publishes a message to a topic.
78
+ * @param topic - The topic to publish the message to.
79
+ * @param message - The message payload to publish.
80
+ * @param _options - Optional configuration options.
81
+ * @returns A promise resolved when the message has been published to the topic.
82
+ */
83
+ public async publish<T>(
84
+ topic: string,
85
+ message: T,
86
+ _options?: any
87
+ ): Promise<void> {
88
+ const q = this.getQueueForTopic(topic);
89
+ await q.add("message", message);
90
+ }
91
+
92
+ /**
93
+ * Subscribes a handler to a topic.
94
+ * @param topic - The topic to subscribe to.
95
+ * @param handler - The handler to invoke when a message is received from the topic.
96
+ * @param _options - Optional configuration options.
97
+ * @returns A promise resolved when the handler has been subscribed to the topic.
98
+ */
99
+ public async subscribe<T>(
100
+ topic: string,
101
+ handler: (msg: BrokerMessage<T>) => Promise<void> | void,
102
+ _options?: any
103
+ ): Promise<void> {
104
+ // register handler
105
+ if (!this.topicHandlers.has(topic))
106
+ this.topicHandlers.set(topic, new Set());
107
+ this.topicHandlers.get(topic)!.add(handler as TopicHandler<T>);
108
+
109
+ // ensure a worker exists for the topic
110
+ this.getWorkerForTopic(topic);
111
+ }
112
+
113
+ /**
114
+ * Unsubscribes a handler from a topic.
115
+ * @param topic - The topic to unsubscribe from.
116
+ * @param handler - The handler to remove from the topic.
117
+ * @returns A promise resolved when the handler has been removed from the topic.
118
+ */
119
+ public async unsubscribe<T>(
120
+ topic: string,
121
+ handler: (msg: BrokerMessage<T>) => Promise<void> | void
122
+ ): Promise<void> {
123
+ const set = this.topicHandlers.get(topic);
124
+ if (set) {
125
+ set.delete(handler as TopicHandler<T>);
126
+ }
127
+ }
128
+
129
+ /**
130
+ * Gracefully shuts down the broker, closing all workers and queues.
131
+ * @returns A promise resolved when all workers and queues have been closed.
132
+ */
133
+ public async close(): Promise<void> {
134
+ // graceful shutdown
135
+ for (const w of this.workers.values()) await w.close();
136
+ for (const q of this.queues.values()) await q.close();
137
+ this.workers.clear();
138
+ this.queues.clear();
139
+ this.topicHandlers.clear();
140
+ }
141
+ }
@@ -0,0 +1,3 @@
1
+ export * from "./basic-types";
2
+ export * from "./queue";
3
+ export * from "./worker";