codeweaver 1.1.0 → 2.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.
@@ -1,7 +1,6 @@
1
1
  import { Router, Request, Response } from "express";
2
2
  import asyncHandler from "express-async-handler";
3
3
  import ProductController from "./product.controller";
4
- import { sendError } from "@/utilities";
5
4
 
6
5
  const router = Router();
7
6
  const productController = new ProductController();
@@ -105,9 +104,7 @@ router.get(
105
104
  "/:id",
106
105
  asyncHandler(async (req: Request, res: Response) => {
107
106
  const product = await productController.get(req.params.id);
108
-
109
- if ("id" in product == false) sendError(res, product);
110
- else res.json(product);
107
+ res.json(product);
111
108
  })
112
109
  );
113
110
 
@@ -168,9 +165,7 @@ router.put(
168
165
  "/:id",
169
166
  asyncHandler(async (req: Request, res: Response) => {
170
167
  const product = await productController.update(req.params.id, req.body);
171
-
172
- if ("id" in product == false) sendError(res, product);
173
- else res.json(product);
168
+ res.json(product);
174
169
  })
175
170
  );
176
171
 
@@ -198,9 +193,7 @@ router.delete(
198
193
  "/:id",
199
194
  asyncHandler(async (req: Request, res: Response) => {
200
195
  const product = await productController.delete(req.params.id);
201
-
202
- if ("id" in product == false) sendError(res, product);
203
- else res.json(product);
196
+ res.json(product);
204
197
  })
205
198
  );
206
199
 
@@ -1,14 +1,16 @@
1
- import { onError, rateLimit, timeout } from "utils-decorators";
1
+ import { memoizeAsync, onError, rateLimit, timeout } from "utils-decorators";
2
2
  import {
3
3
  Product,
4
4
  ProductCreationDto,
5
+ ProductDto,
5
6
  ProductUpdateDto,
6
7
  ZodProductCreationDto,
7
8
  ZodProductUpdateDto,
8
9
  } from "./dto/product.dto";
9
- import { Validate, ZodInput } from "@pkg/ts-zod-decorators";
10
- import { ResponseError } from "@/types";
11
- import { tryParseId } from "@/utilities";
10
+ import { Validate, ZodInput } from "ts-zod4-decorators";
11
+ import { ResponseError } from "@/utilities/types";
12
+ import { parseId } from "@/utilities/error-handling";
13
+ import config from "@/config";
12
14
 
13
15
  // Array to store products (as a mock database)
14
16
  const products: Product[] = [
@@ -89,18 +91,12 @@ const products: Product[] = [
89
91
 
90
92
  function exceedHandler() {
91
93
  const message = "Too much call in allowed window";
92
-
93
- throw new Error(message, {
94
- cause: { status: 500, message } satisfies ResponseError,
95
- });
94
+ throw new ResponseError(message, 429);
96
95
  }
97
96
 
98
97
  function getProductErrorHandler(e: Error) {
99
- const message = "User not found.";
100
-
101
- throw new Error(message, {
102
- cause: { status: 404, message, details: e.message } satisfies ResponseError,
103
- });
98
+ const message = "Product not found.";
99
+ throw new ResponseError(message, 404, e.message);
104
100
  }
105
101
 
106
102
  /**
@@ -110,8 +106,8 @@ function getProductErrorHandler(e: Error) {
110
106
  */
111
107
  export default class ProductController {
112
108
  @rateLimit({
113
- timeSpanMs: 60000,
114
- allowedCalls: 300,
109
+ timeSpanMs: config.rateLimitTimeSpan,
110
+ allowedCalls: config.rateLimitAllowedCalls,
115
111
  exceedHandler,
116
112
  })
117
113
  @Validate
@@ -122,41 +118,40 @@ export default class ProductController {
122
118
  */
123
119
  public async create(
124
120
  @ZodInput(ZodProductCreationDto) product: ProductCreationDto
125
- ) {
121
+ ): Promise<ProductDto> {
126
122
  products.push({
127
123
  ...product,
128
124
  id: products.length + 1,
129
125
  } satisfies Product);
130
126
 
131
- return product;
127
+ return product as ProductDto;
132
128
  }
133
129
 
134
- @timeout(20000)
130
+ @memoizeAsync(config.memoizeTime)
131
+ @timeout(config.timeout)
135
132
  @rateLimit({
136
- timeSpanMs: 60000,
137
- allowedCalls: 300,
133
+ timeSpanMs: config.rateLimitTimeSpan,
134
+ allowedCalls: config.rateLimitAllowedCalls,
138
135
  exceedHandler,
139
136
  })
140
137
  /**
141
138
  * Retrieves all products with truncated descriptions
142
139
  * @returns List of products with summarized descriptions
143
140
  */
144
- public async getAll(): Promise<Product[]> {
145
- return products.map(
146
- (product) =>
147
- ({
148
- ...product,
149
- description: product.description?.substring(0, 50) + "..." || "",
150
- } satisfies Product)
151
- );
141
+ public async getAll(): Promise<ProductDto[]> {
142
+ return products.map((product) => ({
143
+ ...product,
144
+ description: product.description?.substring(0, 50) + "..." || "",
145
+ }));
152
146
  }
153
147
 
148
+ @memoizeAsync(config.memoizeTime)
154
149
  @onError({
155
150
  func: getProductErrorHandler,
156
151
  })
157
152
  @rateLimit({
158
- timeSpanMs: 60000,
159
- allowedCalls: 300,
153
+ timeSpanMs: config.rateLimitTimeSpan,
154
+ allowedCalls: config.rateLimitAllowedCalls,
160
155
  exceedHandler,
161
156
  })
162
157
  /**
@@ -164,23 +159,16 @@ export default class ProductController {
164
159
  * @param id - Product ID as string
165
160
  * @returns Product details or error object if not found
166
161
  */
167
- public async get(id: string): Promise<Product | ResponseError> {
168
- const productId = tryParseId(id);
169
- if (typeof productId != "number") return productId satisfies ResponseError;
162
+ public async get(id: string): Promise<ProductDto> {
163
+ const productId = parseId(id);
170
164
  const product = products.find((product) => product.id === productId);
171
-
172
- if (!product)
173
- return {
174
- status: 404,
175
- message: "Product dose not exist.",
176
- } satisfies ResponseError;
177
-
178
- return product satisfies Product;
165
+ if (product == null) throw new ResponseError("User dose not exist.", 404);
166
+ return product;
179
167
  }
180
168
 
181
169
  @rateLimit({
182
- timeSpanMs: 60000,
183
- allowedCalls: 300,
170
+ timeSpanMs: config.rateLimitTimeSpan,
171
+ allowedCalls: config.rateLimitAllowedCalls,
184
172
  exceedHandler,
185
173
  })
186
174
  @Validate
@@ -188,50 +176,42 @@ export default class ProductController {
188
176
  * Updates an existing product
189
177
  * @param {string} id - Product ID to update
190
178
  * @param {ProductUpdateDto} updateData - Partial product data to update
191
- * @returns {Promise<Product | ResponseError>} Updated product or error object
179
+ * @returns {Promise<Product>} Updated product or error object
192
180
  * @throws {ResponseError} 404 - Product not found
193
181
  * @throws {ResponseError} 400 - Invalid ID format or update data
194
182
  */
195
183
  public async update(
196
184
  id: string,
197
185
  @ZodInput(ZodProductUpdateDto) updateData: ProductUpdateDto
198
- ): Promise<Product | ResponseError> {
186
+ ): Promise<ProductDto> {
199
187
  const product = await this.get(id);
200
188
  if ("id" in product == false) return product satisfies ResponseError;
201
189
 
202
- if (product) Object.assign(product, updateData);
203
- else
204
- return {
205
- status: 404,
206
- message: "Product not found",
207
- } satisfies ResponseError;
190
+ if (product) {
191
+ Object.assign(product, updateData);
192
+ } else {
193
+ throw new ResponseError("Product dose not exist.", 404);
194
+ }
208
195
 
209
196
  return product;
210
197
  }
211
198
 
212
199
  @rateLimit({
213
- timeSpanMs: 60000,
214
- allowedCalls: 300,
200
+ timeSpanMs: config.rateLimitTimeSpan,
201
+ allowedCalls: config.rateLimitAllowedCalls,
215
202
  exceedHandler,
216
203
  })
217
204
  /**
218
205
  * Deletes a product by ID
219
206
  * @param {string} id - Product ID to delete
220
- * @returns {Promise<Product | ResponseError>} Deleted product or error object
207
+ * @returns {Promise<Product>} Deleted product or error object
221
208
  * @throws {ResponseError} 404 - Product not found
222
209
  * @throws {ResponseError} 400 - Invalid ID format
223
210
  */
224
- public async delete(id: string): Promise<Product | ResponseError> {
225
- const productId = tryParseId(id);
226
- if (typeof productId != "number") return productId satisfies ResponseError;
211
+ public async delete(id: string): Promise<ProductDto> {
212
+ const productId = parseId(id);
227
213
  const index = products.findIndex((product) => product.id === productId);
228
-
229
- if (index == -1)
230
- return {
231
- status: 404,
232
- message: "Product dose not exist.",
233
- } satisfies ResponseError;
234
-
235
- return products.splice(index, 1)[0] satisfies Product;
214
+ if (index == -1) throw new ResponseError("Product dose not exist.", 404);
215
+ return products.splice(index, 1)[0];
236
216
  }
237
217
  }
@@ -11,11 +11,13 @@ import { z } from "zod";
11
11
  export const ZodUser = z.object({
12
12
  id: z.number().min(1).int(),
13
13
  username: z.string().min(3),
14
- email: z.string().email(),
14
+ email: z.email(),
15
15
  password: z.string().min(6),
16
16
  });
17
17
 
18
18
  export const ZodUserCreationDto = ZodUser.omit({ id: true });
19
+ export const ZodUserDto = ZodUser.omit({ password: true });
19
20
 
20
21
  export type User = z.infer<typeof ZodUser>;
21
22
  export type UserCreationDto = z.infer<typeof ZodUserCreationDto>;
23
+ export type UserDto = z.infer<typeof ZodUserDto>;
@@ -1,7 +1,6 @@
1
1
  import { Router, Request, Response } from "express";
2
2
  import asyncHandler from "express-async-handler";
3
3
  import UserController from "./user.controller";
4
- import { sendError } from "@/utilities";
5
4
 
6
5
  const router = Router();
7
6
  const userController = new UserController();
@@ -73,9 +72,7 @@ router.get(
73
72
  "/:id",
74
73
  asyncHandler(async (req: Request, res: Response) => {
75
74
  const user = await userController.get(req.params.id);
76
-
77
- if ("id" in user == false) sendError(res, user);
78
- else res.json(user);
75
+ res.json(user);
79
76
  })
80
77
  );
81
78
 
@@ -1,8 +1,14 @@
1
- import { User, ZodUserCreationDto, UserCreationDto } from "./dto/user.dto";
2
- import { onError, rateLimit, timeout } from "utils-decorators";
3
- import { Validate, ZodInput } from "@pkg/ts-zod-decorators";
4
- import { ResponseError } from "@/types";
5
- import { tryParseId } from "@/utilities";
1
+ import {
2
+ User,
3
+ ZodUserCreationDto,
4
+ UserCreationDto,
5
+ UserDto,
6
+ } from "./dto/user.dto";
7
+ import { memoizeAsync, onError, rateLimit, timeout } from "utils-decorators";
8
+ import { Validate, ZodInput } from "ts-zod4-decorators";
9
+ import { ResponseError } from "@/utilities/types";
10
+ import { parseId } from "@/utilities/error-handling";
11
+ import config from "@/config";
6
12
 
7
13
  // Array to store users (as a mock database)
8
14
  const users = [
@@ -70,18 +76,12 @@ const users = [
70
76
 
71
77
  function exceedHandler() {
72
78
  const message = "Too much call in allowed window";
73
-
74
- throw new Error(message, {
75
- cause: { status: 500, message } satisfies ResponseError,
76
- });
79
+ throw new ResponseError(message, 429);
77
80
  }
78
81
 
79
82
  function getUserErrorHandler(e: Error) {
80
83
  const message = "User not found.";
81
-
82
- throw new Error(message, {
83
- cause: { status: 404, message, details: e.message } satisfies ResponseError,
84
- });
84
+ throw new ResponseError(message, 404, e.message);
85
85
  }
86
86
 
87
87
  /**
@@ -93,8 +93,8 @@ export default class UserController {
93
93
  // constructor(private readonly userService: UserService) { }
94
94
 
95
95
  @rateLimit({
96
- timeSpanMs: 60000,
97
- allowedCalls: 300,
96
+ timeSpanMs: config.rateLimitTimeSpan,
97
+ allowedCalls: config.rateLimitAllowedCalls,
98
98
  exceedHandler,
99
99
  })
100
100
  @Validate
@@ -106,42 +106,37 @@ export default class UserController {
106
106
  * @throws {ResponseError} 400 - Invalid input data
107
107
  */
108
108
  public async create(@ZodInput(ZodUserCreationDto) user: UserCreationDto) {
109
- users.push({ ...user, id: users.length + 1 } satisfies User);
109
+ users.push({ ...user, id: users.length + 1 });
110
110
  }
111
111
 
112
+ @memoizeAsync(config.memoizeTime)
112
113
  @onError({
113
114
  func: getUserErrorHandler,
114
115
  })
115
116
  @rateLimit({
116
- timeSpanMs: 60000,
117
- allowedCalls: 300,
117
+ timeSpanMs: config.rateLimitTimeSpan,
118
+ allowedCalls: config.rateLimitAllowedCalls,
118
119
  exceedHandler,
119
120
  })
120
121
  /**
121
122
  * Get user by ID
122
123
  * @param {string} id - User ID as string
123
- * @returns {Promise<User | ResponseError>} User details or error object
124
+ * @returns {Promise<User>} User details or error object
124
125
  * @throws {ResponseError} 404 - User not found
125
126
  * @throws {ResponseError} 400 - Invalid ID format
126
127
  */
127
- public async get(id: string): Promise<User | ResponseError> {
128
- const userId = tryParseId(id);
129
- if (typeof userId != "number") return userId satisfies ResponseError;
130
- const user = users.find((user) => user.id === userId);
131
-
132
- if (!user)
133
- return {
134
- status: 404,
135
- message: "User dose not exist.",
136
- } satisfies ResponseError;
137
-
128
+ public async get(id: string): Promise<UserDto> {
129
+ const response = parseId(id);
130
+ const user = users.find((user) => user.id === response);
131
+ if (user == null) throw new ResponseError("User dose not exist.", 404);
138
132
  return user satisfies User;
139
133
  }
140
134
 
141
- @timeout(20000)
135
+ @memoizeAsync(config.memoizeTime)
136
+ @timeout(config.timeout)
142
137
  @rateLimit({
143
- timeSpanMs: 60000,
144
- allowedCalls: 300,
138
+ timeSpanMs: config.rateLimitTimeSpan,
139
+ allowedCalls: config.rateLimitAllowedCalls,
145
140
  exceedHandler,
146
141
  })
147
142
  /**
@@ -149,13 +144,10 @@ export default class UserController {
149
144
  * @returns {Promise<User[]>} List of users with hidden password fields
150
145
  * @throws {ResponseError} 500 - When rate limit exceeded
151
146
  */
152
- public async getAll(): Promise<User[]> {
153
- return users.map(
154
- (user) =>
155
- ({
156
- ...user,
157
- password: "?",
158
- } satisfies User)
159
- );
147
+ public async getAll(): Promise<UserDto[]> {
148
+ return users.map((user) => ({
149
+ ...user,
150
+ password: "?",
151
+ }));
160
152
  }
161
153
  }
@@ -0,0 +1,54 @@
1
+ import {
2
+ memoizeTime,
3
+ productionEnvironment,
4
+ rateLimitTimeSpan,
5
+ rateLimitAllowedCalls,
6
+ timeout,
7
+ portNumber,
8
+ } from "./constants";
9
+
10
+ /**
11
+ * Server configuration interface
12
+ * @interface
13
+ * @property {string} url - Base server URL
14
+ */
15
+ interface Server {
16
+ url: string;
17
+ }
18
+
19
+ /**
20
+ * API information structure
21
+ * @interface
22
+ * @property {string} title - API title
23
+ * @property {string} version - API version
24
+ * @property {string} description - API description
25
+ */
26
+ interface Info {
27
+ title: string;
28
+ version: string;
29
+ description: string;
30
+ }
31
+
32
+ /**
33
+ * Swagger definition structure
34
+ * @interface
35
+ * @property {string} openApi - OpenAPI specification version
36
+ * @property {Info} info - API information
37
+ * @property {Server[]} servers - List of server configurations
38
+ */
39
+ interface SwaggerDefinition {
40
+ openApi: string;
41
+ info: Info;
42
+ servers: Server[];
43
+ }
44
+
45
+ /**
46
+ * Swagger configuration options
47
+ * @interface
48
+ * @property {SwaggerDefinition} swaggerDefinition - Swagger definition object
49
+ * @property {string[]} apis - Paths to API documentation files
50
+ */
51
+ export interface SwaggerOptions {
52
+ swaggerDefinition: SwaggerDefinition;
53
+ apis: string[];
54
+ }
@@ -0,0 +1,81 @@
1
+ import { z, ZodRawShape } from "zod";
2
+ import { ResponseError } from "./types";
3
+
4
+ /**
5
+ * Strictly convert obj (type T1) to T2 using a Zod schema.
6
+ *
7
+ * - Throws if obj has extra fields beyond those defined in the schema.
8
+ * - Validates fields with the schema; on failure, throws with a descriptive message.
9
+ * - Returns an object typed as T2 (inferred from the schema).
10
+ *
11
+ * @param obj - Source object of type T1
12
+ * @param schema - Zod schema describing the target type T2
13
+ * @returns T2 inferred from the provided schema
14
+ */
15
+ export function assignStrictlyFromSchema<T1 extends object, T2 extends object>(
16
+ obj: T1,
17
+ schema: z.ZodObject<any>
18
+ ): T2 {
19
+ // 1) Derive the runtime keys from the schema's shape
20
+ const shape = (schema as any)._def?.shape as ZodRawShape | undefined;
21
+ if (!shape) {
22
+ throw new ResponseError(
23
+ "assignStrictlyFromSchema: provided schema has no shape.",
24
+ 500
25
+ );
26
+ }
27
+
28
+ const keysSchema = Object.keys(shape) as Array<keyof any>;
29
+
30
+ // 2) Extra keys check
31
+ const objKeys = Object.keys(obj) as Array<keyof T1>;
32
+ const extraKeys = objKeys.filter((k) => !keysSchema.includes(k as any));
33
+ if (extraKeys.length > 0) {
34
+ throw new ResponseError(
35
+ `assignStrictlyFromSchema: source object contains extra field(s) not present on target: ${extraKeys.join(
36
+ ", "
37
+ )}`,
38
+ 500
39
+ );
40
+ }
41
+
42
+ // 3) Required-field check for T2 (all keys in schema must be present and non-undefined)
43
+ const missingOrUndefined = keysSchema.filter((k) => {
44
+ const v = (obj as any)[k];
45
+ return v === undefined || v === null;
46
+ });
47
+ if (missingOrUndefined.length > 0) {
48
+ throw new ResponseError(
49
+ `assignStrictlyFromSchema: missing required field(s): ${missingOrUndefined.join(
50
+ ", "
51
+ )}`,
52
+ 500
53
+ );
54
+ }
55
+
56
+ // 4) Build a plain object to pass through Zod for validation
57
+ const candidate: any = {};
58
+ for (const k of keysSchema) {
59
+ if (k in (obj as any)) {
60
+ candidate[k] = (obj as any)[k];
61
+ }
62
+ }
63
+
64
+ // 5) Validate against the schema
65
+ const result = schema.safeParse(candidate);
66
+ if (!result.success) {
67
+ // Modern, non-format error reporting
68
+ const issues = result.error.issues.map((i) => ({
69
+ path: i.path, // where the issue occurred
70
+ message: i.message, // human-friendly message
71
+ code: i.code, // e.g., "too_small", "invalid_type"
72
+ }));
73
+ // You can log issues or throw a structured error
74
+ throw new Error(
75
+ `assignStrictlyFromSchema: validation failed: ${JSON.stringify(issues)}`
76
+ );
77
+ }
78
+
79
+ // 6) Return the validated data typed as T2
80
+ return result.data as T2;
81
+ }
@@ -0,0 +1,120 @@
1
+ import { Response } from "express";
2
+ import { ReturnInfo, ResponseError } from "./types";
3
+
4
+ /**
5
+ * Sends a standardized HTTP error response.
6
+ *
7
+ * This function sets the response status from the provided error (defaulting to 500)
8
+ * and serializes the error object as JSON.
9
+ *
10
+ * @param res - Express Response object to send the error on
11
+ * @param error - Error details to return to the client (must include status or default will be 500)
12
+ */
13
+ export function sendHttpError(res: Response, error: ResponseError): void {
14
+ res.status(error.status ?? 500).json(error);
15
+ }
16
+
17
+ /**
18
+ * Executes a function and captures a potential error as a ReturnInfo tuple.
19
+ *
20
+ * Returns a two-element tuple: [value, error]
21
+ * - value: the function result if it succeeds; null if an exception is thrown
22
+ * - error: the caught Error wrapped as a ResponseError (or the provided error) if the function throws; null if the function succeeds
23
+ *
24
+ * This utility helps avoid try/catch blocks at call sites by returning both the
25
+ * result and any error in a single value.
26
+ *
27
+ * @template T
28
+ * @param func - The function to execute
29
+ * @param error - The error object to return when an exception occurs (typically a ResponseError). If no error is provided, null is used.
30
+ * @returns ReturnInfo<T> A tuple: [value or null, error or null]
31
+ */
32
+ export function tryCatch<T>(
33
+ func: () => T,
34
+ error: ResponseError | null
35
+ ): ReturnInfo<T> {
36
+ try {
37
+ return [func(), null];
38
+ } catch {
39
+ return [null, error];
40
+ }
41
+ }
42
+
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
+ /**
68
+ * Creates a successful result from a ReturnInfo tuple.
69
+ *
70
+ * Given a ReturnInfo<T> of the form [value, error], this returns the value
71
+ * when the operation succeeded, or null when there was an error.
72
+ *
73
+ * @template T
74
+ * @param input - The ReturnInfo tuple
75
+ * @returns The successful value of type T, or null if there was an error
76
+ */
77
+ export function successfulResult<T>(input: ReturnInfo<T>): T | null {
78
+ return input[0];
79
+ }
80
+
81
+ /**
82
+ * Normalizes and wraps an error into the common ReturnInfo shape.
83
+ *
84
+ * The function accepts a ReturnInfo-shaped input and extracts the error portion.
85
+ * If a non-Error value is provided, you should wrap it as a ResponseError beforehand.
86
+ * If the error is already a ResponseError, it is returned as-is.
87
+ *
88
+ * @template T
89
+ * @param responseError - The error to wrap, either as a ResponseError or as a ReturnInfo<T> where the error is at index 1
90
+ * @returns The extracted or wrapped ResponseError, or null if there is no error
91
+ */
92
+ export function error<T>(responseError: ReturnInfo<T>): ResponseError | null {
93
+ return responseError[1];
94
+ }
95
+
96
+ /**
97
+ * Determines whether a ReturnInfo value represents a successful operation.
98
+ *
99
+ * A result is considered successful when there is no error (i.e., the error portion is null).
100
+ *
101
+ * @template T
102
+ * @param result - The ReturnInfo tuple [value | null, error | null]
103
+ * @returns true if there is no error; false otherwise
104
+ */
105
+ export function isSuccessful<T>(result: ReturnInfo<T>): boolean {
106
+ return result[1] === null;
107
+ }
108
+
109
+ /**
110
+ * Indicates whether a ReturnInfo value represents an error.
111
+ *
112
+ * This is the logical negation of isSuccess for a given ReturnInfo.
113
+ *
114
+ * @template T
115
+ * @param result - The ReturnInfo tuple [value | null, error | null]
116
+ * @returns true if an error is present (i.e., error is not null); false otherwise
117
+ */
118
+ export function hasError<T>(result: ReturnInfo<T>): boolean {
119
+ return result[1] !== null;
120
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Represents a standardized error response structure for API endpoints
3
+ * @class
4
+ * @property {number} [status] - HTTP status code
5
+ * @property {string} [name] - Error name/type
6
+ * @property {string} message - Human-readable error message
7
+ * @property {string} [stack] - Error stack trace (development only)
8
+ * @property {string} [details] - Additional error details
9
+ */
10
+ export class ResponseError extends Error {
11
+ public constructor(
12
+ public message: string,
13
+ public status?: number,
14
+ public details?: string,
15
+ public code?: string,
16
+ public stack?: string
17
+ ) {
18
+ super(message);
19
+ }
20
+ }
21
+
22
+ export type ReturnInfo<T> = [T | null, ResponseError | null];
23
+ export type AsyncReturnInfo<T> = Promise<[T | null, ResponseError | null]>;