codeweaver 2.0.0 → 2.2.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.
- package/README.md +84 -144
- package/package.json +11 -3
- package/src/config.ts +11 -50
- package/src/constants.ts +1 -0
- package/src/db.ts +183 -0
- package/src/entities/order.entity.ts +68 -0
- package/src/entities/product.entity.ts +75 -0
- package/src/entities/user.entity.ts +38 -0
- package/src/main.ts +19 -7
- package/src/routers/orders/dto/order.dto.ts +54 -30
- package/src/routers/orders/index.router.ts +12 -7
- package/src/routers/orders/order.controller.ts +87 -79
- package/src/routers/products/dto/product.dto.ts +86 -31
- package/src/routers/products/index.router.ts +13 -6
- package/src/routers/products/product.controller.ts +109 -120
- package/src/routers/users/dto/user.dto.ts +13 -19
- package/src/routers/users/index.router.ts +7 -4
- package/src/routers/users/user.controller.ts +72 -98
- package/src/swagger-options.ts +7 -22
- package/src/utilities/assign.ts +44 -59
- package/src/utilities/cache/memory-cache.ts +74 -0
- package/src/utilities/cache/redis-cache.ts +111 -0
- package/src/utilities/container.ts +159 -0
- package/src/utilities/conversion.ts +158 -0
- package/src/utilities/error-handling.ts +98 -26
- package/src/utilities/logger/base-logger.interface.ts +11 -0
- package/src/utilities/logger/logger.config.ts +95 -0
- package/src/utilities/logger/logger.service.ts +122 -0
- package/src/utilities/logger/winston-logger.service.ts +16 -0
- package/src/utilities/types.ts +0 -23
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import "reflect-metadata";
|
|
2
|
+
|
|
3
|
+
export type Constructor<T = any> = new (...args: any[]) => T;
|
|
4
|
+
|
|
5
|
+
interface Provider<T = any> {
|
|
6
|
+
/** The concrete class to instantiate (may be the same as token or a different implementation). */
|
|
7
|
+
useClass: Constructor<T>;
|
|
8
|
+
/** Cached singleton instance, if one has been created. */
|
|
9
|
+
instance?: T;
|
|
10
|
+
/** Whether to treat the provider as a singleton. Defaults to true. */
|
|
11
|
+
singleton?: boolean;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const InjectableRegistry: Array<Constructor<any>> = [];
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* A tiny dependency injection container.
|
|
18
|
+
*
|
|
19
|
+
* Features:
|
|
20
|
+
* - Register tokens (classes) with optional options.
|
|
21
|
+
* - Resolve instances with automatic dependency resolution via design:paramtypes metadata.
|
|
22
|
+
* - Simple singleton lifetime by default (one instance per token).
|
|
23
|
+
*
|
|
24
|
+
* Notes:
|
|
25
|
+
* - Requires reflect-metadata to be loaded and "emitDecoratorMetadata"/"experimentalDecorators"
|
|
26
|
+
* enabled in tsconfig for design:paramtypes to exist.
|
|
27
|
+
*/
|
|
28
|
+
class Container {
|
|
29
|
+
private registrations = new Map<Constructor, Provider>();
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Register a class as a provider for the given token.
|
|
33
|
+
*
|
|
34
|
+
* If no options.useClass is provided, the token itself is used as the concrete class.
|
|
35
|
+
* If options.singleton is not provided, the provider defaults to singleton = true.
|
|
36
|
+
*
|
|
37
|
+
* @param token - The token (class constructor) that represents the dependency.
|
|
38
|
+
* @param options - Optional provider options.
|
|
39
|
+
* - useClass?: The concrete class to instantiate for this token.
|
|
40
|
+
* - singleton?: Whether to reuse a single instance (default: true).
|
|
41
|
+
*/
|
|
42
|
+
register<T>(
|
|
43
|
+
token: Constructor<T>,
|
|
44
|
+
options?: { useClass?: Constructor<T>; singleton?: boolean }
|
|
45
|
+
): void {
|
|
46
|
+
const useClass = options?.useClass ?? token;
|
|
47
|
+
const singleton = options?.singleton ?? true;
|
|
48
|
+
this.registrations.set(token, { useClass, singleton });
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Resolve an instance for the given token.
|
|
53
|
+
*
|
|
54
|
+
* Behavior:
|
|
55
|
+
* - If the token is not registered, attempt to instantiate directly (without DI).
|
|
56
|
+
* - If a singleton instance already exists for the token, return it.
|
|
57
|
+
* - Otherwise, resolve the concrete class (provider.useClass), construct it
|
|
58
|
+
* by recursively resolving its constructor parameter types, and cache
|
|
59
|
+
* the instance if singleton is true.
|
|
60
|
+
*
|
|
61
|
+
* @param token - The token (class constructor) to resolve.
|
|
62
|
+
* @returns An instance of the requested type T.
|
|
63
|
+
*/
|
|
64
|
+
resolve<T>(token: Constructor<T>): T {
|
|
65
|
+
const provider = this.registrations.get(token);
|
|
66
|
+
|
|
67
|
+
if (!provider) {
|
|
68
|
+
// If not registered, try to instantiate directly (without DI)
|
|
69
|
+
return this.construct(token);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (provider.instance) {
|
|
73
|
+
return provider.instance;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Resolve dependencies for the concrete class
|
|
77
|
+
const target = provider.useClass;
|
|
78
|
+
const instance = this.construct(target);
|
|
79
|
+
|
|
80
|
+
if (provider.singleton) {
|
|
81
|
+
provider.instance = instance;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return instance;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Internal helper to instantiate a class, resolving its constructor dependencies
|
|
89
|
+
* via design:paramtypes metadata.
|
|
90
|
+
*
|
|
91
|
+
* @param constructorFunction - The constructor function of the class to instantiate.
|
|
92
|
+
* @returns A new instance of type T with dependencies injected.
|
|
93
|
+
*/
|
|
94
|
+
private construct<T>(constructorFunction: Constructor<T>): T {
|
|
95
|
+
const paramTypes: any[] =
|
|
96
|
+
Reflect.getMetadata("design:paramtypes", constructorFunction) || [];
|
|
97
|
+
const args = paramTypes.map((paramType) => this.resolve(paramType));
|
|
98
|
+
return new constructorFunction(...args);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Bootstraps a container by wiring up all registered injectables.
|
|
103
|
+
*
|
|
104
|
+
* This helper walks the InjectableRegistry and registers each class with the container,
|
|
105
|
+
* honoring per-class options (like singleton) that may have been stored as metadata.
|
|
106
|
+
*
|
|
107
|
+
* Why this exists:
|
|
108
|
+
* - Keeps your registration centralized and automatic, so you don't have to call
|
|
109
|
+
* container.register(...) repeatedly for every service.
|
|
110
|
+
*
|
|
111
|
+
* Usage reminder:
|
|
112
|
+
* - Ensure Reflect Metadata is loaded before calling this, so that the di:injectable
|
|
113
|
+
* metadata is actually readable.
|
|
114
|
+
*/
|
|
115
|
+
function bootstrapContainer(container: Container) {
|
|
116
|
+
for (const constructorFunction of InjectableRegistry) {
|
|
117
|
+
// Read per-class options if you added metadata
|
|
118
|
+
const meta = Reflect.getMetadata("di:injectable", constructorFunction) as
|
|
119
|
+
| { singleton?: boolean }
|
|
120
|
+
| undefined;
|
|
121
|
+
const singleton = meta?.singleton ?? false;
|
|
122
|
+
container.register(constructorFunction, { singleton });
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Marks a class for automatic DI registration.
|
|
128
|
+
* Options:
|
|
129
|
+
* - singleton: whether to register as a singleton (default true)
|
|
130
|
+
* - token: optional explicit token to register for (defaults to class constructor)
|
|
131
|
+
*/
|
|
132
|
+
export function Injectable(options?: {
|
|
133
|
+
singleton?: boolean;
|
|
134
|
+
token?: any;
|
|
135
|
+
}): ClassDecorator {
|
|
136
|
+
return (target: any) => {
|
|
137
|
+
// Push into registry for later registration
|
|
138
|
+
InjectableRegistry.push(target);
|
|
139
|
+
// You could also attach metadata if you want to customize per-class
|
|
140
|
+
Reflect.defineMetadata("di:injectable", options ?? {}, target);
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** A single, shared DI container for the app. */
|
|
145
|
+
const container = new Container();
|
|
146
|
+
bootstrapContainer(container);
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Resolve a service wherever needed.
|
|
150
|
+
*
|
|
151
|
+
* Example
|
|
152
|
+
*
|
|
153
|
+
* import { resolve } from "@/utilities/container";
|
|
154
|
+
* const loggerService = resolve(LoggerService);
|
|
155
|
+
* loggerService.log(...);
|
|
156
|
+
*/
|
|
157
|
+
export function resolve<T>(token: Constructor<T>): T {
|
|
158
|
+
return container.resolve(token);
|
|
159
|
+
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { z, ZodRawShape } from "zod";
|
|
2
|
+
import { ResponseError } from "./error-handling";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Helper: normalize and validate a numeric string for integer parsing.
|
|
6
|
+
* This ensures we reject non-integer strings, empty input, or inputs with extra chars.
|
|
7
|
+
*/
|
|
8
|
+
function parseIntegerStrict(input: string): number {
|
|
9
|
+
// Trim whitespace
|
|
10
|
+
const s = input.trim();
|
|
11
|
+
|
|
12
|
+
// Empty or just sign is invalid
|
|
13
|
+
if (s.length === 0 || s === "+" || s === "-") {
|
|
14
|
+
throw new Error("Invalid integer");
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Use a regex to ensure the entire string is an optional sign followed by digits
|
|
18
|
+
if (!/^[+-]?\d+$/.test(s)) {
|
|
19
|
+
throw new Error("Invalid integer");
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Safe parse
|
|
23
|
+
const n = Number(s);
|
|
24
|
+
if (!Number.isSafeInteger(n)) {
|
|
25
|
+
throw new Error("Integer out of safe range");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return n;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Parses a string input into an integer number with strict validation.
|
|
33
|
+
*
|
|
34
|
+
* If parsing fails, this function throws a ResponseError describing the invalid input.
|
|
35
|
+
*
|
|
36
|
+
* @param input - The string to parse as an integer
|
|
37
|
+
* @returns The parsed integer
|
|
38
|
+
* @throws {ResponseError} When the input cannot be parsed as an integer
|
|
39
|
+
*/
|
|
40
|
+
export function stringToInteger(input: string): number {
|
|
41
|
+
try {
|
|
42
|
+
return parseIntegerStrict(input);
|
|
43
|
+
} catch {
|
|
44
|
+
throw new ResponseError(
|
|
45
|
+
"The input parameter must be a valid integer.",
|
|
46
|
+
400
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Parses a string input into a boolean with explicit validation.
|
|
53
|
+
*
|
|
54
|
+
* Accepted true values: "true", "1", "yes", case-insensitive
|
|
55
|
+
* Accepted false values: "false", "0", "no", case-insensitive
|
|
56
|
+
* Any other input is invalid.
|
|
57
|
+
*
|
|
58
|
+
* If parsing fails, this function throws a ResponseError describing the invalid input.
|
|
59
|
+
*
|
|
60
|
+
* @param input - The string to parse as a boolean
|
|
61
|
+
* @returns The parsed boolean
|
|
62
|
+
* @throws {ResponseError} When the input cannot be parsed as a boolean
|
|
63
|
+
*/
|
|
64
|
+
export function stringToBoolean(input: string): boolean {
|
|
65
|
+
const s = input.trim().toLowerCase();
|
|
66
|
+
|
|
67
|
+
if (["true", "1", "yes", "y"].includes(s)) {
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
if (["false", "0", "no", "n"].includes(s)) {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
throw new ResponseError(
|
|
75
|
+
"The input parameter must be a boolean (e.g., true/false, 1/0).",
|
|
76
|
+
400
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Parses a string input into a number with basic validation.
|
|
82
|
+
*
|
|
83
|
+
* If parsing fails, this function throws a ResponseError describing the invalid input.
|
|
84
|
+
*
|
|
85
|
+
* @param input - The string to parse as a number
|
|
86
|
+
* @returns The parsed number
|
|
87
|
+
* @throws {ResponseError} When the input cannot be parsed as a number
|
|
88
|
+
*/
|
|
89
|
+
export function stringToNumber(input: string): number {
|
|
90
|
+
try {
|
|
91
|
+
// Trim and convert
|
|
92
|
+
const n = Number(input.trim());
|
|
93
|
+
|
|
94
|
+
// Allow finite numbers only
|
|
95
|
+
if (!Number.isFinite(n)) {
|
|
96
|
+
throw new Error("Invalid number");
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return n;
|
|
100
|
+
} catch {
|
|
101
|
+
throw new ResponseError("The input parameter must be a valid number.", 400);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Strictly convert obj (type T1) to T2 using a Zod schema.
|
|
107
|
+
*
|
|
108
|
+
* - Extras in obj are ignored (no throw).
|
|
109
|
+
* - Validates fields with the schema; on failure, throws with a descriptive message.
|
|
110
|
+
* - Returns an object typed as T2 (inferred from the schema).
|
|
111
|
+
*
|
|
112
|
+
* @param obj - Source object of type T1
|
|
113
|
+
* @param schema - Zod schema describing the target type T2
|
|
114
|
+
* @returns T2 inferred from the provided schema
|
|
115
|
+
*/
|
|
116
|
+
export function convert<T1 extends object, T2 extends object>(
|
|
117
|
+
obj: T1,
|
|
118
|
+
schema: z.ZodObject<any>
|
|
119
|
+
): T2 {
|
|
120
|
+
// 1) Derive the runtime keys from the schema's shape
|
|
121
|
+
const shape = (schema as any)._def?.shape as ZodRawShape | undefined;
|
|
122
|
+
if (!shape) {
|
|
123
|
+
throw new ResponseError(
|
|
124
|
+
"convertStrictlyFromSchema: provided schema has no shape.",
|
|
125
|
+
500
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const keysSchema = Object.keys(shape) as Array<keyof any>;
|
|
130
|
+
|
|
131
|
+
// 2) Build a plain object to pass through Zod for validation
|
|
132
|
+
// Include only keys that exist on the schema (ignore extras in obj)
|
|
133
|
+
const candidate: any = {};
|
|
134
|
+
for (const k of keysSchema) {
|
|
135
|
+
if ((obj as any).hasOwnProperty(k)) {
|
|
136
|
+
candidate[k] = (obj as any)[k];
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// 3) Validate against the schema
|
|
141
|
+
const result = schema.safeParse(candidate);
|
|
142
|
+
if (!result.success) {
|
|
143
|
+
// Modern, non-format error reporting
|
|
144
|
+
const issues = result.error.issues.map((i) => ({
|
|
145
|
+
path: i.path, // where the issue occurred
|
|
146
|
+
message: i.message, // human-friendly message
|
|
147
|
+
code: i.code, // e.g., "too_small", "invalid_type"
|
|
148
|
+
}));
|
|
149
|
+
// You can log issues or throw a structured error
|
|
150
|
+
throw new ResponseError(
|
|
151
|
+
`convertStrictlyFromSchema: validation failed: ${JSON.stringify(issues)}`,
|
|
152
|
+
500
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// 4) Return the validated data typed as T2
|
|
157
|
+
return result.data as T2;
|
|
158
|
+
}
|
|
@@ -1,5 +1,53 @@
|
|
|
1
1
|
import { Response } from "express";
|
|
2
|
-
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Represents a standardized error response structure for API endpoints.
|
|
5
|
+
*
|
|
6
|
+
* This class models an API-friendly error, carrying a human message plus
|
|
7
|
+
* optional metadata (status, details, input, code, stack). Extends the built-in Error
|
|
8
|
+
* so it works naturally with try/catch blocks.
|
|
9
|
+
*/
|
|
10
|
+
export class ResponseError extends Error {
|
|
11
|
+
public constructor(
|
|
12
|
+
/**
|
|
13
|
+
* User-facing error message describing what went wrong.
|
|
14
|
+
*/
|
|
15
|
+
public message: string,
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Optional HTTP status code related to the error (e.g., 400, 404, 500).
|
|
19
|
+
*/
|
|
20
|
+
public status?: number,
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Optional human-readable details or context about the error.
|
|
24
|
+
*/
|
|
25
|
+
public details?: string,
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Optional input value that caused the error (useful for logging/diagnostics).
|
|
29
|
+
*/
|
|
30
|
+
public input?: string,
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Optional application-specific error code (e.g., "INVALID_INPUT").
|
|
34
|
+
*/
|
|
35
|
+
public code?: string,
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Optional stack trace string (usually provided by runtime).
|
|
39
|
+
* Note: In many environments, stack is inherited from Error; you may
|
|
40
|
+
* not need to redefine it here unless you have a specific reason.
|
|
41
|
+
*/
|
|
42
|
+
public stack?: string
|
|
43
|
+
) {
|
|
44
|
+
// Ensure the base Error class gets the message for standard properties like name, stack, etc.
|
|
45
|
+
super(message);
|
|
46
|
+
|
|
47
|
+
// If a custom stack is provided, you might assign it; otherwise, the runtime stack will be used.
|
|
48
|
+
if (stack) this.stack = stack;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
3
51
|
|
|
4
52
|
/**
|
|
5
53
|
* Sends a standardized HTTP error response.
|
|
@@ -14,6 +62,18 @@ export function sendHttpError(res: Response, error: ResponseError): void {
|
|
|
14
62
|
res.status(error.status ?? 500).json(error);
|
|
15
63
|
}
|
|
16
64
|
|
|
65
|
+
/**
|
|
66
|
+
* A generic alias representing a tuple of [result, error].
|
|
67
|
+
* - result is either T or null if an error occurred
|
|
68
|
+
* - error is either a ResponseError or null if the operation succeeded
|
|
69
|
+
*/
|
|
70
|
+
export type ReturnInfo<T> = [T | null, ResponseError | null];
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* A Promise-wrapped version of ReturnInfo.
|
|
74
|
+
*/
|
|
75
|
+
export type AsyncReturnInfo<T> = Promise<ReturnInfo<T>>;
|
|
76
|
+
|
|
17
77
|
/**
|
|
18
78
|
* Executes a function and captures a potential error as a ReturnInfo tuple.
|
|
19
79
|
*
|
|
@@ -29,7 +89,7 @@ export function sendHttpError(res: Response, error: ResponseError): void {
|
|
|
29
89
|
* @param error - The error object to return when an exception occurs (typically a ResponseError). If no error is provided, null is used.
|
|
30
90
|
* @returns ReturnInfo<T> A tuple: [value or null, error or null]
|
|
31
91
|
*/
|
|
32
|
-
export function
|
|
92
|
+
export function invoke<T>(
|
|
33
93
|
func: () => T,
|
|
34
94
|
error: ResponseError | null
|
|
35
95
|
): ReturnInfo<T> {
|
|
@@ -40,30 +100,6 @@ export function tryCatch<T>(
|
|
|
40
100
|
}
|
|
41
101
|
}
|
|
42
102
|
|
|
43
|
-
/**
|
|
44
|
-
* Parses a string input into a number (ID) with basic validation.
|
|
45
|
-
*
|
|
46
|
-
* If parsing fails, this function throws a ResponseError describing the invalid input.
|
|
47
|
-
*
|
|
48
|
-
* @param input - The string to parse as an integer ID
|
|
49
|
-
* @returns The parsed number
|
|
50
|
-
* @throws {ResponseError} When the input cannot be parsed as an integer
|
|
51
|
-
*/
|
|
52
|
-
export function parseId(input: string): number {
|
|
53
|
-
try {
|
|
54
|
-
// parseInt may yield NaN for non-numeric input; this example mirrors the original behavior
|
|
55
|
-
// If you want stricter validation, you can check isNaN and throw a more explicit error.
|
|
56
|
-
return parseInt(input);
|
|
57
|
-
} catch {
|
|
58
|
-
throw new ResponseError(
|
|
59
|
-
input,
|
|
60
|
-
400,
|
|
61
|
-
"Wrong input",
|
|
62
|
-
"The id parameter must be an integer number."
|
|
63
|
-
);
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
103
|
/**
|
|
68
104
|
* Creates a successful result from a ReturnInfo tuple.
|
|
69
105
|
*
|
|
@@ -118,3 +154,39 @@ export function isSuccessful<T>(result: ReturnInfo<T>): boolean {
|
|
|
118
154
|
export function hasError<T>(result: ReturnInfo<T>): boolean {
|
|
119
155
|
return result[1] !== null;
|
|
120
156
|
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Indicates whether a ReturnInfo value represents an error.
|
|
160
|
+
*
|
|
161
|
+
* This is the logical negation of isSuccess for a given ReturnInfo.
|
|
162
|
+
*
|
|
163
|
+
* @template T
|
|
164
|
+
* @param result - The ReturnInfo tuple [value | null, error | null]
|
|
165
|
+
* @returns true if an error is present (i.e., error is not null); false otherwise
|
|
166
|
+
*/
|
|
167
|
+
export function then<T>(
|
|
168
|
+
result: ReturnInfo<T>,
|
|
169
|
+
callback: (object: T) => void
|
|
170
|
+
): void {
|
|
171
|
+
if (isSuccessful(result)) {
|
|
172
|
+
callback(successfulResult(result)!);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Indicates whether a ReturnInfo value represents an error.
|
|
178
|
+
*
|
|
179
|
+
* This is the logical negation of isSuccess for a given ReturnInfo.
|
|
180
|
+
*
|
|
181
|
+
* @template T
|
|
182
|
+
* @param result - The ReturnInfo tuple [value | null, error | null]
|
|
183
|
+
* @returns true if an error is present (i.e., error is not null); false otherwise
|
|
184
|
+
*/
|
|
185
|
+
export function catchError<T>(
|
|
186
|
+
result: ReturnInfo<T>,
|
|
187
|
+
callback: (error: ResponseError) => void
|
|
188
|
+
): void {
|
|
189
|
+
if (hasError(result)) {
|
|
190
|
+
callback(error(result)!);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// src/logging/BaseLogger.ts
|
|
2
|
+
|
|
3
|
+
export type LogLevel = "fatal" | "error" | "warn" | "info" | "debug" | "trace";
|
|
4
|
+
|
|
5
|
+
export interface Meta {
|
|
6
|
+
[key: string]: unknown;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface BaseLogger {
|
|
10
|
+
log(level: LogLevel, message: string, meta?: Meta): void;
|
|
11
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import * as winston from "winston";
|
|
2
|
+
import { TransformableInfo } from "logform";
|
|
3
|
+
import "winston-daily-rotate-file"; // Import the DailyRotateFile transport to extend winston transports
|
|
4
|
+
|
|
5
|
+
// Console format: colorized and pretty printed
|
|
6
|
+
// Custom formatting to include timestamp, context, level, and message
|
|
7
|
+
//
|
|
8
|
+
// Example: "2023-10-05T12:00:00.000Z [MyContext] info: This is a log message"
|
|
9
|
+
const consoleFormat = winston.format.combine(
|
|
10
|
+
winston.format.colorize({ all: true }),
|
|
11
|
+
winston.format.timestamp(),
|
|
12
|
+
winston.format.printf(
|
|
13
|
+
({
|
|
14
|
+
timestamp,
|
|
15
|
+
level,
|
|
16
|
+
message,
|
|
17
|
+
context,
|
|
18
|
+
...meta
|
|
19
|
+
}: TransformableInfo): string => {
|
|
20
|
+
const contextStr = context ? `[${context as string}]` : "[main]";
|
|
21
|
+
|
|
22
|
+
const metaStr = Object.keys(meta).length ? JSON.stringify(meta) : "";
|
|
23
|
+
|
|
24
|
+
return `${timestamp as string} ${contextStr} ${level}: ${
|
|
25
|
+
message as string
|
|
26
|
+
} ${metaStr}`;
|
|
27
|
+
}
|
|
28
|
+
)
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
// File format: JSON with timestamp
|
|
32
|
+
// This format is suitable for structured logging and easier parsing by
|
|
33
|
+
// log management systems
|
|
34
|
+
//
|
|
35
|
+
// Example: {"timestamp":"2023-10-05T12:00:00.000Z", "level":"info",
|
|
36
|
+
// "message":"This is a log message", "context":"MyContext"}
|
|
37
|
+
const fileFormat = winston.format.combine(
|
|
38
|
+
winston.format.timestamp(),
|
|
39
|
+
winston.format.json()
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
// Console transport for logging to the console with colorized output
|
|
43
|
+
// and custom formatting that includes timestamp, context, level, and message.
|
|
44
|
+
const consoleTransport = new winston.transports.Console({
|
|
45
|
+
format: consoleFormat,
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// File transport for logging to files with daily rotation.
|
|
49
|
+
// It logs everything at the trace level and formats logs as JSON.
|
|
50
|
+
// The logs are stored in the "logs" directory with a date pattern
|
|
51
|
+
// in the filename.
|
|
52
|
+
// The logs are zipped and rotated daily, keeping logs for 14 days.
|
|
53
|
+
const fileTransport = new winston.transports.DailyRotateFile({
|
|
54
|
+
dirname: "logs",
|
|
55
|
+
filename: "app-%DATE%.log",
|
|
56
|
+
datePattern: "YYYY-MM-DD",
|
|
57
|
+
zippedArchive: true,
|
|
58
|
+
maxSize: "20m",
|
|
59
|
+
maxFiles: "14d",
|
|
60
|
+
level: "trace", // log everything to file / external service
|
|
61
|
+
format: fileFormat,
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// Add custom colors for log levels to enhance console output readability
|
|
65
|
+
// This allows log levels to be displayed in different colors in the console.
|
|
66
|
+
// Define a custom logger with explicit levels
|
|
67
|
+
const customLevels = {
|
|
68
|
+
levels: {
|
|
69
|
+
fatal: 0,
|
|
70
|
+
error: 1,
|
|
71
|
+
warn: 2,
|
|
72
|
+
info: 3,
|
|
73
|
+
debug: 4,
|
|
74
|
+
trace: 5,
|
|
75
|
+
},
|
|
76
|
+
colors: {
|
|
77
|
+
fatal: "red bold",
|
|
78
|
+
error: "red",
|
|
79
|
+
warn: "yellow",
|
|
80
|
+
info: "green",
|
|
81
|
+
debug: "blue",
|
|
82
|
+
trace: "grey",
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
winston.addColors(customLevels.colors);
|
|
87
|
+
|
|
88
|
+
export const logger = winston.createLogger({
|
|
89
|
+
levels: customLevels.levels,
|
|
90
|
+
level: "trace",
|
|
91
|
+
transports: [consoleTransport, fileTransport],
|
|
92
|
+
exitOnError: false,
|
|
93
|
+
exceptionHandlers: [consoleTransport, fileTransport],
|
|
94
|
+
rejectionHandlers: [consoleTransport, fileTransport],
|
|
95
|
+
});
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { BaseLogger, LogLevel, Meta } from "./base-logger.interface";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* LoggerService is a custom logger service that integrates Winston for advanced logging capabilities.
|
|
5
|
+
*/
|
|
6
|
+
export class LoggerService {
|
|
7
|
+
/**
|
|
8
|
+
* Constructs a new LoggerService instance.
|
|
9
|
+
*
|
|
10
|
+
* @param logger - Injected logger instance
|
|
11
|
+
*/
|
|
12
|
+
public constructor(private logger: BaseLogger) {}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Logs an informational message with optional context and metadata.
|
|
16
|
+
* This method combines the provided metadata with the context and log level,
|
|
17
|
+
* and sends the log entry to the appropriate transports.
|
|
18
|
+
*
|
|
19
|
+
* @param message - The message to log
|
|
20
|
+
* @param context - Optional context information (e.g., module or method name)
|
|
21
|
+
* @param meta - Additional metadata to include in the log entry
|
|
22
|
+
*/
|
|
23
|
+
public log(level: LogLevel, message: string, meta: Meta = {}): void {
|
|
24
|
+
meta.timestamp = new Date().toISOString();
|
|
25
|
+
this.logger.log(level, message, meta);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Logs an informational message with optional context and metadata.
|
|
30
|
+
* This method combines the provided metadata with the context and log level,
|
|
31
|
+
* and sends the log entry to the appropriate transports.
|
|
32
|
+
*
|
|
33
|
+
* @param message - The message to log
|
|
34
|
+
* @param context - Optional context information (e.g., module or method name)
|
|
35
|
+
* @param meta - Additional metadata to include in the log entry
|
|
36
|
+
*/
|
|
37
|
+
public info(message: string, context?: string, meta: Meta = {}): void {
|
|
38
|
+
this.log("info", message, { context, ...meta });
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Logs a fatal error message with optional trace, context, and metadata.
|
|
43
|
+
* This method combines the provided metadata with the trace and context,
|
|
44
|
+
* and sends the log entry to the appropriate transports.
|
|
45
|
+
*
|
|
46
|
+
* @param message - The fatal error message to log
|
|
47
|
+
* @param trace - Optional trace information for the error
|
|
48
|
+
* @param context - Optional context information (e.g., module or method name)
|
|
49
|
+
* @param meta - Additional metadata to include in the log entry
|
|
50
|
+
*/
|
|
51
|
+
public fatal(
|
|
52
|
+
message: string,
|
|
53
|
+
context?: string,
|
|
54
|
+
trace?: string,
|
|
55
|
+
meta: Meta = {}
|
|
56
|
+
): void {
|
|
57
|
+
this.log("fatal", message, { context, trace, ...meta });
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Logs an error message with optional trace, context, and metadata.
|
|
62
|
+
* This method combines the provided metadata with the trace and context,
|
|
63
|
+
* and sends the log entry to the appropriate transports.
|
|
64
|
+
*
|
|
65
|
+
* @param message - The error message to log
|
|
66
|
+
* @param trace - Optional trace information for the error
|
|
67
|
+
* @param context - Optional context information (e.g., module or method name)
|
|
68
|
+
* @param meta - Additional metadata to include in the log entry
|
|
69
|
+
*/
|
|
70
|
+
public error(
|
|
71
|
+
message: string,
|
|
72
|
+
context?: string,
|
|
73
|
+
trace?: string,
|
|
74
|
+
meta: Meta = {}
|
|
75
|
+
): void {
|
|
76
|
+
this.log("error", message, { context, trace, ...meta });
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Logs a warning message with optional context and metadata.
|
|
81
|
+
* This method combines the provided metadata with the context and log level,
|
|
82
|
+
* and sends the log entry to the appropriate transports.
|
|
83
|
+
*
|
|
84
|
+
* @param message - The warning message to log
|
|
85
|
+
* @param context - Optional context information (e.g., module or method name)
|
|
86
|
+
* @param meta - Additional metadata to include in the log entry
|
|
87
|
+
*/
|
|
88
|
+
public warn(
|
|
89
|
+
message: string,
|
|
90
|
+
context?: string,
|
|
91
|
+
trace?: string,
|
|
92
|
+
...meta: unknown[]
|
|
93
|
+
): void {
|
|
94
|
+
this.log("warn", message, { context, trace, ...meta });
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Logs a debug message with optional context and metadata.
|
|
99
|
+
* This method combines the provided metadata with the context and log level,
|
|
100
|
+
* and sends the log entry to the appropriate transports.
|
|
101
|
+
*
|
|
102
|
+
* @param message - The debug message to log
|
|
103
|
+
* @param context - Optional context information (e.g., module or method name)
|
|
104
|
+
* @param meta - Additional metadata to include in the log entry
|
|
105
|
+
*/
|
|
106
|
+
public debug(message: string, context?: string, ...meta: unknown[]): void {
|
|
107
|
+
this.log("debug", message, { context, ...meta });
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Logs a trace message with optional context and metadata.
|
|
112
|
+
* This method combines the provided metadata with the context and log level,
|
|
113
|
+
* and sends the log entry to the appropriate transports.
|
|
114
|
+
*
|
|
115
|
+
* @param message - The trace message to log
|
|
116
|
+
* @param context - Optional context information (e.g., module or method name)
|
|
117
|
+
* @param meta - Additional metadata to include in the log entry
|
|
118
|
+
*/
|
|
119
|
+
public trace(message: string, context?: string, ...meta: unknown[]): void {
|
|
120
|
+
this.log("trace", message, { context, ...meta });
|
|
121
|
+
}
|
|
122
|
+
}
|