codeweaver 1.0.15 → 1.1.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.
@@ -0,0 +1,237 @@
1
+ import { onError, rateLimit, timeout } from "utils-decorators";
2
+ import {
3
+ Product,
4
+ ProductCreationDto,
5
+ ProductUpdateDto,
6
+ ZodProductCreationDto,
7
+ ZodProductUpdateDto,
8
+ } from "./dto/product.dto";
9
+ import { Validate, ZodInput } from "@pkg/ts-zod-decorators";
10
+ import { ResponseError } from "@/types";
11
+ import { tryParseId } from "@/utilities";
12
+
13
+ // Array to store products (as a mock database)
14
+ const products: Product[] = [
15
+ {
16
+ id: 1,
17
+ name: "ASUS ROG Zephyrus G15",
18
+ price: 45000000,
19
+ description: "Gaming laptop with AMD Ryzen 9 5900HS and RTX 3080 GPU",
20
+ category: "Electronics",
21
+ stock: 15,
22
+ },
23
+ {
24
+ id: 2,
25
+ name: "Sony WH-1000XM5 Wireless Headphones",
26
+ price: 12000000,
27
+ description:
28
+ "Premium noise-canceling over-ear headphones with 30hr battery",
29
+ category: "Electronics",
30
+ stock: 8,
31
+ },
32
+ {
33
+ id: 3,
34
+ name: "LG Smart Inverter Microwave",
35
+ price: 25000000,
36
+ description: "1.7 cu.ft countertop microwave with smart sensor cooking",
37
+ category: "Appliances",
38
+ stock: 5,
39
+ },
40
+ {
41
+ id: 4,
42
+ name: "Trek Marlin 5 Mountain Bike",
43
+ price: 18000000,
44
+ description: "Entry-level mountain bike with aluminum frame and 21 speeds",
45
+ category: "Sports",
46
+ stock: 3,
47
+ },
48
+ {
49
+ id: 5,
50
+ name: "DeLonghi Espresso Machine",
51
+ price: 6500000,
52
+ description: "Compact espresso maker with manual milk frother",
53
+ category: "Kitchen",
54
+ stock: 12,
55
+ },
56
+ {
57
+ id: 6,
58
+ name: "Anker Wireless Charger",
59
+ price: 1200000,
60
+ description: "15W fast wireless charger with anti-slip surface",
61
+ category: "Mobile Accessories",
62
+ stock: 30,
63
+ },
64
+ {
65
+ id: 7,
66
+ name: "Logitech MX Master 3 Mouse",
67
+ price: 4500000,
68
+ description: "Ergonomic wireless mouse with Darkfield tracking",
69
+ category: "Computer Accessories",
70
+ stock: 18,
71
+ },
72
+ {
73
+ id: 8,
74
+ name: "Kindle Paperwhite",
75
+ price: 3800000,
76
+ description: 'Waterproof e-reader with 6.8" 300ppi display',
77
+ category: "Electronics",
78
+ stock: 9,
79
+ },
80
+ {
81
+ id: 9,
82
+ name: "Dyson V11 Vacuum Cleaner",
83
+ price: 32000000,
84
+ description: "Cordless stick vacuum with LCD screen and 60min runtime",
85
+ category: "Home Appliances",
86
+ stock: 7,
87
+ },
88
+ ];
89
+
90
+ function exceedHandler() {
91
+ const message = "Too much call in allowed window";
92
+
93
+ throw new Error(message, {
94
+ cause: { status: 500, message } satisfies ResponseError,
95
+ });
96
+ }
97
+
98
+ 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
+ });
104
+ }
105
+
106
+ /**
107
+ * Controller for handling product-related operations
108
+ * @class ProductController
109
+ * @desc Provides methods for product management including CRUD operations
110
+ */
111
+ export default class ProductController {
112
+ @rateLimit({
113
+ timeSpanMs: 60000,
114
+ allowedCalls: 300,
115
+ exceedHandler,
116
+ })
117
+ @Validate
118
+ /**
119
+ * Creates a new product with validated data
120
+ * @param product - Product creation data validated by Zod schema
121
+ * @returns Newly created product with generated ID
122
+ */
123
+ public async create(
124
+ @ZodInput(ZodProductCreationDto) product: ProductCreationDto
125
+ ) {
126
+ products.push({
127
+ ...product,
128
+ id: products.length + 1,
129
+ } satisfies Product);
130
+
131
+ return product;
132
+ }
133
+
134
+ @timeout(20000)
135
+ @rateLimit({
136
+ timeSpanMs: 60000,
137
+ allowedCalls: 300,
138
+ exceedHandler,
139
+ })
140
+ /**
141
+ * Retrieves all products with truncated descriptions
142
+ * @returns List of products with summarized descriptions
143
+ */
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
+ );
152
+ }
153
+
154
+ @onError({
155
+ func: getProductErrorHandler,
156
+ })
157
+ @rateLimit({
158
+ timeSpanMs: 60000,
159
+ allowedCalls: 300,
160
+ exceedHandler,
161
+ })
162
+ /**
163
+ * Finds a product by its ID
164
+ * @param id - Product ID as string
165
+ * @returns Product details or error object if not found
166
+ */
167
+ public async get(id: string): Promise<Product | ResponseError> {
168
+ const productId = tryParseId(id);
169
+ if (typeof productId != "number") return productId satisfies ResponseError;
170
+ 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;
179
+ }
180
+
181
+ @rateLimit({
182
+ timeSpanMs: 60000,
183
+ allowedCalls: 300,
184
+ exceedHandler,
185
+ })
186
+ @Validate
187
+ /**
188
+ * Updates an existing product
189
+ * @param {string} id - Product ID to update
190
+ * @param {ProductUpdateDto} updateData - Partial product data to update
191
+ * @returns {Promise<Product | ResponseError>} Updated product or error object
192
+ * @throws {ResponseError} 404 - Product not found
193
+ * @throws {ResponseError} 400 - Invalid ID format or update data
194
+ */
195
+ public async update(
196
+ id: string,
197
+ @ZodInput(ZodProductUpdateDto) updateData: ProductUpdateDto
198
+ ): Promise<Product | ResponseError> {
199
+ const product = await this.get(id);
200
+ if ("id" in product == false) return product satisfies ResponseError;
201
+
202
+ if (product) Object.assign(product, updateData);
203
+ else
204
+ return {
205
+ status: 404,
206
+ message: "Product not found",
207
+ } satisfies ResponseError;
208
+
209
+ return product;
210
+ }
211
+
212
+ @rateLimit({
213
+ timeSpanMs: 60000,
214
+ allowedCalls: 300,
215
+ exceedHandler,
216
+ })
217
+ /**
218
+ * Deletes a product by ID
219
+ * @param {string} id - Product ID to delete
220
+ * @returns {Promise<Product | ResponseError>} Deleted product or error object
221
+ * @throws {ResponseError} 404 - Product not found
222
+ * @throws {ResponseError} 400 - Invalid ID format
223
+ */
224
+ public async delete(id: string): Promise<Product | ResponseError> {
225
+ const productId = tryParseId(id);
226
+ if (typeof productId != "number") return productId satisfies ResponseError;
227
+ 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;
236
+ }
237
+ }
@@ -1,9 +1,21 @@
1
1
  import { z } from "zod";
2
2
 
3
- export const CreateUserDto = z.object({
3
+ /**
4
+ * Zod schema for User entity
5
+ * @typedef {Object} ZodUser
6
+ * @property {number} id - Unique identifier (min 1)
7
+ * @property {string} username - Username (min 3 chars)
8
+ * @property {string} email - Valid email format
9
+ * @property {string} password - Password (min 6 chars)
10
+ */
11
+ export const ZodUser = z.object({
12
+ id: z.number().min(1).int(),
4
13
  username: z.string().min(3),
5
14
  email: z.string().email(),
6
15
  password: z.string().min(6),
7
16
  });
8
17
 
9
- export type CreateUser = z.infer<typeof CreateUserDto>;
18
+ export const ZodUserCreationDto = ZodUser.omit({ id: true });
19
+
20
+ export type User = z.infer<typeof ZodUser>;
21
+ export type UserCreationDto = z.infer<typeof ZodUserCreationDto>;
@@ -1,6 +1,7 @@
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";
4
5
 
5
6
  const router = Router();
6
7
  const userController = new UserController();
@@ -11,70 +12,87 @@ const userController = new UserController();
11
12
  * post:
12
13
  * summary: Create a user
13
14
  * description: Create a new user.
14
- * requestBody:
15
- * required: true
16
- * content:
17
- * application/json:
18
- * schema:
19
- * type: object
20
- * required:
21
- * - username
22
- * - email
23
- * - password
24
- * properties:
25
- * username:
26
- * type: string
27
- * email:
28
- * type: string
29
- * password:
30
- * type: string
31
- * example: # Sample object
32
- * username: JessicaSmith
33
- * email: william.howard.taft@my-own-personal-domain.com
34
- * password: password123
15
+ * consumes:
16
+ * - application/json
17
+ * produces:
18
+ * - application/json
19
+ * parameters:
20
+ * - in: body
21
+ * name: user
22
+ * required: true
23
+ * schema:
24
+ * type: object
25
+ * required:
26
+ * - username
27
+ * - email
28
+ * - password
29
+ * properties:
30
+ * username:
31
+ * type: string
32
+ * minLength: 3
33
+ * example: JessicaSmith
34
+ * email:
35
+ * type: string
36
+ * format: email
37
+ * example: user@example.com
38
+ * password:
39
+ * type: string
40
+ * minLength: 6
41
+ * example: securePassword123
35
42
  * responses:
36
43
  * 201:
37
- * description: product created
44
+ * description: User created
38
45
  */
39
46
  router.post(
40
47
  "/",
41
48
  asyncHandler(async (req: Request, res: Response) => {
42
- const result = await userController.createUser(req.body);
43
- res.status(201).json({ message: result });
49
+ const user = await userController.create(req.body);
50
+ res.status(201).json(user);
44
51
  })
45
52
  );
46
53
 
47
54
  /**
48
55
  * @swagger
49
- * /user:
56
+ * /users/{id}:
50
57
  * get:
51
- * summary: Get user home
52
- * description: Returns the user home page.
58
+ * summary: Get a user by ID
59
+ * parameters:
60
+ * - name: id
61
+ * in: path
62
+ * required: true
63
+ * description: The ID of the product
64
+ * schema:
65
+ * type: integer
53
66
  * responses:
54
67
  * 200:
55
- * description: User home page
68
+ * description: A user object
69
+ * 404:
70
+ * description: user not found
56
71
  */
57
72
  router.get(
58
- "/",
73
+ "/:id",
59
74
  asyncHandler(async (req: Request, res: Response) => {
60
- res.send("User Home");
75
+ const user = await userController.get(req.params.id);
76
+
77
+ if ("id" in user == false) sendError(res, user);
78
+ else res.json(user);
61
79
  })
62
80
  );
63
81
 
64
82
  /**
65
83
  * @swagger
66
- * /user/profile:
84
+ * /users:
67
85
  * get:
68
- * summary: Get user profile
69
- * description: Returns user profile information.
86
+ * summary: Get users
87
+ * description: Returns a list of user objects.
70
88
  * responses:
71
89
  * 200:
72
- * description: User profile information
90
+ * description: A list of user objects
73
91
  */
74
92
  router.get(
75
- "/profile",
93
+ "/",
76
94
  asyncHandler(async (req: Request, res: Response) => {
77
- res.send("User Profile");
95
+ res.json(await userController.getAll());
78
96
  })
79
97
  );
80
98
 
@@ -1,33 +1,161 @@
1
- import { Validate, ZodInput } from "ts-zod-decorators";
2
- import { CreateUserDto, CreateUser } from "./dto/user.dto";
3
- import { onError, rateLimit, throttle } from "utils-decorators";
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";
6
+
7
+ // Array to store users (as a mock database)
8
+ const users = [
9
+ {
10
+ id: 1,
11
+ username: "johndoe",
12
+ email: "johndoe@gmail.com",
13
+ password: "S3cur3P@ssw0rd",
14
+ },
15
+ {
16
+ id: 2,
17
+ username: "janesmith",
18
+ email: "janesmith@yahoo.com",
19
+ password: "P@ssw0rd2024",
20
+ },
21
+ {
22
+ id: 3,
23
+ username: "michael89",
24
+ email: "michael89@hotmail.com",
25
+ password: "M1chael!2024",
26
+ },
27
+ {
28
+ id: 4,
29
+ username: "lisa.wong",
30
+ email: "lisa.wong@example.com",
31
+ password: "L1saW0ng!2024",
32
+ },
33
+ {
34
+ id: 5,
35
+ username: "alex_k",
36
+ email: "alex.k@gmail.com",
37
+ password: "A1ex#Key2024",
38
+ },
39
+ {
40
+ id: 6,
41
+ username: "emilyj",
42
+ email: "emilyj@hotmail.com",
43
+ password: "Em!ly0101",
44
+ },
45
+ {
46
+ id: 7,
47
+ username: "davidparker",
48
+ email: "david.parker@yahoo.com",
49
+ password: "D@v!d2024",
50
+ },
51
+ {
52
+ id: 8,
53
+ username: "sophia_m",
54
+ email: "sophia.m@gmail.com",
55
+ password: "Sophi@2024",
56
+ },
57
+ {
58
+ id: 9,
59
+ username: "chrisw",
60
+ email: "chrisw@outlook.com",
61
+ password: "Chri$Wong21",
62
+ },
63
+ {
64
+ id: 10,
65
+ username: "natalie_b",
66
+ email: "natalie_b@gmail.com",
67
+ password: "N@talie#B2024",
68
+ },
69
+ ];
4
70
 
5
71
  function exceedHandler() {
6
- throw new Error("Too much call in allowed window");
72
+ const message = "Too much call in allowed window";
73
+
74
+ throw new Error(message, {
75
+ cause: { status: 500, message } satisfies ResponseError,
76
+ });
7
77
  }
8
78
 
9
- function errorHandler(e: Error): void {
10
- console.error(e);
79
+ function getUserErrorHandler(e: Error) {
80
+ const message = "User not found.";
81
+
82
+ throw new Error(message, {
83
+ cause: { status: 404, message, details: e.message } satisfies ResponseError,
84
+ });
11
85
  }
12
86
 
87
+ /**
88
+ * Controller for handling user-related operations
89
+ * @class UserController
90
+ * @desc Provides methods for user management including CRUD operations
91
+ */
13
92
  export default class UserController {
14
- //constructor(private readonly userService: UserService) { }
93
+ // constructor(private readonly userService: UserService) { }
15
94
 
16
- // Throttle the createUser method to 1 request per 200 milliseconds
17
95
  @rateLimit({
18
96
  timeSpanMs: 60000,
19
97
  allowedCalls: 300,
20
98
  exceedHandler,
21
99
  })
100
+ @Validate
101
+ /**
102
+ * Create a new user
103
+ * @param {UserCreationDto} user - User creation data validated by Zod schema
104
+ * @returns {Promise<void>}
105
+ * @throws {ResponseError} 500 - When rate limit exceeded
106
+ * @throws {ResponseError} 400 - Invalid input data
107
+ */
108
+ public async create(@ZodInput(ZodUserCreationDto) user: UserCreationDto) {
109
+ users.push({ ...user, id: users.length + 1 } satisfies User);
110
+ }
111
+
22
112
  @onError({
23
- func: errorHandler,
113
+ func: getUserErrorHandler,
24
114
  })
25
- @Validate
26
- public async createUser(
27
- @ZodInput(CreateUserDto) data: CreateUser
28
- ): Promise<string> {
29
- // Here you can include logic to save user to database
30
- console.log("Creating user:", data);
31
- return "User created successfully";
115
+ @rateLimit({
116
+ timeSpanMs: 60000,
117
+ allowedCalls: 300,
118
+ exceedHandler,
119
+ })
120
+ /**
121
+ * Get user by ID
122
+ * @param {string} id - User ID as string
123
+ * @returns {Promise<User | ResponseError>} User details or error object
124
+ * @throws {ResponseError} 404 - User not found
125
+ * @throws {ResponseError} 400 - Invalid ID format
126
+ */
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
+
138
+ return user satisfies User;
139
+ }
140
+
141
+ @timeout(20000)
142
+ @rateLimit({
143
+ timeSpanMs: 60000,
144
+ allowedCalls: 300,
145
+ exceedHandler,
146
+ })
147
+ /**
148
+ * Get all users with masked passwords
149
+ * @returns {Promise<User[]>} List of users with hidden password fields
150
+ * @throws {ResponseError} 500 - When rate limit exceeded
151
+ */
152
+ public async getAll(): Promise<User[]> {
153
+ return users.map(
154
+ (user) =>
155
+ ({
156
+ ...user,
157
+ password: "?",
158
+ } satisfies User)
159
+ );
32
160
  }
33
161
  }
package/src/types.ts ADDED
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Represents a standardized error response structure for API endpoints
3
+ * @interface
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 interface ResponseError {
11
+ status: number;
12
+ name?: string;
13
+ message: string;
14
+ stack?: string;
15
+ details?: string;
16
+ }
@@ -0,0 +1,47 @@
1
+ import { Response } from "express";
2
+ import { ResponseError } from "./types";
3
+
4
+ /**
5
+ * Sends a standardized error response
6
+ * @param {Response} res - Express response object
7
+ * @param {ResponseError} error - Error details object
8
+ */
9
+ export function sendError(res: Response, error: ResponseError): void {
10
+ res.status(error.status).json(error);
11
+ }
12
+
13
+ /**
14
+ * Parses and validates ID parameter from string to number
15
+ * @param {string} input - Input string to parse
16
+ * @returns {number|ResponseError} Parsed number or error object
17
+ */
18
+ export function tryParseId(input: string): number | ResponseError {
19
+ try {
20
+ return parseInt(input) satisfies number;
21
+ } catch {
22
+ return {
23
+ status: 400,
24
+ message: "wrong input",
25
+ details: "The id parameter must be an integer number.",
26
+ } satisfies ResponseError;
27
+ }
28
+ }
29
+
30
+ /**
31
+ * Checks if the provided object is a ResponseError.
32
+ *
33
+ * A ResponseError is an object that contains at least the properties:
34
+ * - message: string
35
+ * - status: number
36
+ *
37
+ * @param obj - The object to check.
38
+ * @returns true if the object is a ResponseError, false otherwise.
39
+ */
40
+ export function isResponseError(obj: unknown): boolean {
41
+ return (
42
+ obj != null &&
43
+ typeof obj === "object" &&
44
+ "message" in obj &&
45
+ "status" in obj
46
+ );
47
+ }
package/tsconfig.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "compilerOptions": {
3
- "target": "ESNext",
3
+ "target": "ES2023",
4
4
  "module": "commonjs",
5
5
  "outDir": "./dist",
6
6
  "strict": true,
@@ -8,8 +8,14 @@
8
8
  "skipLibCheck": true,
9
9
  "forceConsistentCasingInFileNames": true,
10
10
  "experimentalDecorators": true,
11
- "emitDecoratorMetadata": true
11
+ "emitDecoratorMetadata": true,
12
+ "baseUrl": ".",
13
+ "paths": {
14
+ "@pkg/*": ["src/packages/*"],
15
+ "@r/*": ["src/routers/*"],
16
+ "@/*": ["src/*"]
17
+ }
12
18
  },
13
- "include": ["src/**/*.ts", "src/**/*.js", "command.js"],
19
+ "include": ["**/*.ts", "**/*.js"],
14
20
  "exclude": ["node_modules"]
15
21
  }
@@ -0,0 +1,10 @@
1
+ {
2
+ "compilerOptions": {
3
+ "baseUrl": ".",
4
+ "paths": {
5
+ "@pkg/*": ["dist/packages/*"],
6
+ "@r/*": ["dist/routers/*"],
7
+ "@/*": ["dist/*"]
8
+ }
9
+ }
10
+ }