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.
- package/README.md +56 -73
- package/package.json +23 -1
- package/src/config.ts +17 -15
- package/src/constants.ts +1 -0
- package/src/core/aws/api-gateway.ts +187 -0
- package/src/core/aws/basic-types.ts +147 -0
- package/src/core/aws/dynamodb.ts +187 -0
- package/src/core/aws/index.ts +9 -0
- package/src/core/aws/lambda.ts +199 -0
- package/src/core/aws/message-broker.ts +167 -0
- package/src/core/aws/message.ts +259 -0
- package/src/core/aws/s3.ts +136 -0
- package/src/core/aws/utilities.ts +44 -0
- package/src/core/cache/basic-types.ts +17 -0
- package/src/core/cache/decorator.ts +72 -0
- package/src/core/cache/index.ts +4 -0
- package/src/core/cache/memory-cache.class.ts +119 -0
- package/src/{utilities/cache/redis-cache.ts → core/cache/redis-cache.class.ts} +58 -10
- package/src/core/container/basic-types.ts +10 -0
- package/src/{utilities → core/container}/container.ts +7 -17
- package/src/core/container/index.ts +2 -0
- package/src/{utilities → core/error}/error-handling.ts +1 -65
- package/src/core/error/index.ts +3 -0
- package/src/core/error/response-error.ts +45 -0
- package/src/core/error/send-http-error.ts +15 -0
- package/src/core/file/file-helpers.ts +166 -0
- package/src/core/file/index.ts +1 -0
- package/src/{utilities → core/helpers}/assignment.ts +2 -2
- package/src/core/helpers/comparison.ts +86 -0
- package/src/{utilities → core/helpers}/conversion.ts +2 -2
- package/src/core/helpers/decorators.ts +316 -0
- package/src/core/helpers/format.ts +9 -0
- package/src/core/helpers/index.ts +7 -0
- package/src/core/helpers/range.ts +67 -0
- package/src/core/helpers/types.ts +3 -0
- package/src/core/logger/index.ts +4 -0
- package/src/{utilities/logger/logger.config.ts → core/logger/winston-logger.config.ts} +1 -1
- package/src/{utilities → core}/logger/winston-logger.service.ts +3 -3
- package/src/core/message-broker/bullmq/basic-types.ts +67 -0
- package/src/core/message-broker/bullmq/broker.ts +141 -0
- package/src/core/message-broker/bullmq/index.ts +3 -0
- package/src/core/message-broker/bullmq/queue.ts +58 -0
- package/src/core/message-broker/bullmq/worker.ts +68 -0
- package/src/core/message-broker/kafka/basic-types.ts +45 -0
- package/src/core/message-broker/kafka/consumer.ts +95 -0
- package/src/core/message-broker/kafka/index.ts +3 -0
- package/src/core/message-broker/kafka/producer.ts +113 -0
- package/src/core/message-broker/rabitmq/basic-types.ts +44 -0
- package/src/core/message-broker/rabitmq/channel.ts +95 -0
- package/src/core/message-broker/rabitmq/consumer.ts +94 -0
- package/src/core/message-broker/rabitmq/index.ts +4 -0
- package/src/core/message-broker/rabitmq/producer.ts +100 -0
- package/src/core/message-broker/utilities.ts +50 -0
- package/src/core/middlewares/basic-types.ts +39 -0
- package/src/core/middlewares/decorators.ts +244 -0
- package/src/core/middlewares/index.ts +3 -0
- package/src/core/middlewares/middlewares.ts +246 -0
- package/src/core/parallel/index.ts +3 -0
- package/src/{utilities → core}/parallel/parallel.ts +11 -1
- package/src/core/rate-limit/basic-types.ts +43 -0
- package/src/core/rate-limit/index.ts +4 -0
- package/src/core/rate-limit/memory-store.ts +65 -0
- package/src/core/rate-limit/rate-limit.ts +134 -0
- package/src/core/rate-limit/redis-store.ts +141 -0
- package/src/core/retry/basic-types.ts +21 -0
- package/src/core/retry/decorator.ts +139 -0
- package/src/core/retry/index.ts +2 -0
- package/src/main.ts +6 -8
- package/src/routers/orders/index.router.ts +5 -1
- package/src/routers/orders/order.controller.ts +54 -64
- package/src/routers/products/index.router.ts +2 -1
- package/src/routers/products/product.controller.ts +33 -68
- package/src/routers/users/index.router.ts +1 -1
- package/src/routers/users/user.controller.ts +25 -50
- package/src/utilities/cache/memory-cache.ts +0 -74
- /package/src/{utilities → core}/logger/base-logger.interface.ts +0 -0
- /package/src/{utilities → core}/logger/logger.service.ts +0 -0
- /package/src/{utilities → core}/parallel/chanel.ts +0 -0
- /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,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
|
+
}
|
|
@@ -85,7 +85,7 @@ const customLevels = {
|
|
|
85
85
|
|
|
86
86
|
winston.addColors(customLevels.colors);
|
|
87
87
|
|
|
88
|
-
export const
|
|
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 {
|
|
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(
|
|
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
|
+
}
|