codeweaver 1.0.15 → 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.
@@ -0,0 +1,217 @@
1
+ import { memoizeAsync, onError, rateLimit, timeout } from "utils-decorators";
2
+ import {
3
+ Product,
4
+ ProductCreationDto,
5
+ ProductDto,
6
+ ProductUpdateDto,
7
+ ZodProductCreationDto,
8
+ ZodProductUpdateDto,
9
+ } from "./dto/product.dto";
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";
14
+
15
+ // Array to store products (as a mock database)
16
+ const products: Product[] = [
17
+ {
18
+ id: 1,
19
+ name: "ASUS ROG Zephyrus G15",
20
+ price: 45000000,
21
+ description: "Gaming laptop with AMD Ryzen 9 5900HS and RTX 3080 GPU",
22
+ category: "Electronics",
23
+ stock: 15,
24
+ },
25
+ {
26
+ id: 2,
27
+ name: "Sony WH-1000XM5 Wireless Headphones",
28
+ price: 12000000,
29
+ description:
30
+ "Premium noise-canceling over-ear headphones with 30hr battery",
31
+ category: "Electronics",
32
+ stock: 8,
33
+ },
34
+ {
35
+ id: 3,
36
+ name: "LG Smart Inverter Microwave",
37
+ price: 25000000,
38
+ description: "1.7 cu.ft countertop microwave with smart sensor cooking",
39
+ category: "Appliances",
40
+ stock: 5,
41
+ },
42
+ {
43
+ id: 4,
44
+ name: "Trek Marlin 5 Mountain Bike",
45
+ price: 18000000,
46
+ description: "Entry-level mountain bike with aluminum frame and 21 speeds",
47
+ category: "Sports",
48
+ stock: 3,
49
+ },
50
+ {
51
+ id: 5,
52
+ name: "DeLonghi Espresso Machine",
53
+ price: 6500000,
54
+ description: "Compact espresso maker with manual milk frother",
55
+ category: "Kitchen",
56
+ stock: 12,
57
+ },
58
+ {
59
+ id: 6,
60
+ name: "Anker Wireless Charger",
61
+ price: 1200000,
62
+ description: "15W fast wireless charger with anti-slip surface",
63
+ category: "Mobile Accessories",
64
+ stock: 30,
65
+ },
66
+ {
67
+ id: 7,
68
+ name: "Logitech MX Master 3 Mouse",
69
+ price: 4500000,
70
+ description: "Ergonomic wireless mouse with Darkfield tracking",
71
+ category: "Computer Accessories",
72
+ stock: 18,
73
+ },
74
+ {
75
+ id: 8,
76
+ name: "Kindle Paperwhite",
77
+ price: 3800000,
78
+ description: 'Waterproof e-reader with 6.8" 300ppi display',
79
+ category: "Electronics",
80
+ stock: 9,
81
+ },
82
+ {
83
+ id: 9,
84
+ name: "Dyson V11 Vacuum Cleaner",
85
+ price: 32000000,
86
+ description: "Cordless stick vacuum with LCD screen and 60min runtime",
87
+ category: "Home Appliances",
88
+ stock: 7,
89
+ },
90
+ ];
91
+
92
+ function exceedHandler() {
93
+ const message = "Too much call in allowed window";
94
+ throw new ResponseError(message, 429);
95
+ }
96
+
97
+ function getProductErrorHandler(e: Error) {
98
+ const message = "Product not found.";
99
+ throw new ResponseError(message, 404, e.message);
100
+ }
101
+
102
+ /**
103
+ * Controller for handling product-related operations
104
+ * @class ProductController
105
+ * @desc Provides methods for product management including CRUD operations
106
+ */
107
+ export default class ProductController {
108
+ @rateLimit({
109
+ timeSpanMs: config.rateLimitTimeSpan,
110
+ allowedCalls: config.rateLimitAllowedCalls,
111
+ exceedHandler,
112
+ })
113
+ @Validate
114
+ /**
115
+ * Creates a new product with validated data
116
+ * @param product - Product creation data validated by Zod schema
117
+ * @returns Newly created product with generated ID
118
+ */
119
+ public async create(
120
+ @ZodInput(ZodProductCreationDto) product: ProductCreationDto
121
+ ): Promise<ProductDto> {
122
+ products.push({
123
+ ...product,
124
+ id: products.length + 1,
125
+ } satisfies Product);
126
+
127
+ return product as ProductDto;
128
+ }
129
+
130
+ @memoizeAsync(config.memoizeTime)
131
+ @timeout(config.timeout)
132
+ @rateLimit({
133
+ timeSpanMs: config.rateLimitTimeSpan,
134
+ allowedCalls: config.rateLimitAllowedCalls,
135
+ exceedHandler,
136
+ })
137
+ /**
138
+ * Retrieves all products with truncated descriptions
139
+ * @returns List of products with summarized descriptions
140
+ */
141
+ public async getAll(): Promise<ProductDto[]> {
142
+ return products.map((product) => ({
143
+ ...product,
144
+ description: product.description?.substring(0, 50) + "..." || "",
145
+ }));
146
+ }
147
+
148
+ @memoizeAsync(config.memoizeTime)
149
+ @onError({
150
+ func: getProductErrorHandler,
151
+ })
152
+ @rateLimit({
153
+ timeSpanMs: config.rateLimitTimeSpan,
154
+ allowedCalls: config.rateLimitAllowedCalls,
155
+ exceedHandler,
156
+ })
157
+ /**
158
+ * Finds a product by its ID
159
+ * @param id - Product ID as string
160
+ * @returns Product details or error object if not found
161
+ */
162
+ public async get(id: string): Promise<ProductDto> {
163
+ const productId = parseId(id);
164
+ const product = products.find((product) => product.id === productId);
165
+ if (product == null) throw new ResponseError("User dose not exist.", 404);
166
+ return product;
167
+ }
168
+
169
+ @rateLimit({
170
+ timeSpanMs: config.rateLimitTimeSpan,
171
+ allowedCalls: config.rateLimitAllowedCalls,
172
+ exceedHandler,
173
+ })
174
+ @Validate
175
+ /**
176
+ * Updates an existing product
177
+ * @param {string} id - Product ID to update
178
+ * @param {ProductUpdateDto} updateData - Partial product data to update
179
+ * @returns {Promise<Product>} Updated product or error object
180
+ * @throws {ResponseError} 404 - Product not found
181
+ * @throws {ResponseError} 400 - Invalid ID format or update data
182
+ */
183
+ public async update(
184
+ id: string,
185
+ @ZodInput(ZodProductUpdateDto) updateData: ProductUpdateDto
186
+ ): Promise<ProductDto> {
187
+ const product = await this.get(id);
188
+ if ("id" in product == false) return product satisfies ResponseError;
189
+
190
+ if (product) {
191
+ Object.assign(product, updateData);
192
+ } else {
193
+ throw new ResponseError("Product dose not exist.", 404);
194
+ }
195
+
196
+ return product;
197
+ }
198
+
199
+ @rateLimit({
200
+ timeSpanMs: config.rateLimitTimeSpan,
201
+ allowedCalls: config.rateLimitAllowedCalls,
202
+ exceedHandler,
203
+ })
204
+ /**
205
+ * Deletes a product by ID
206
+ * @param {string} id - Product ID to delete
207
+ * @returns {Promise<Product>} Deleted product or error object
208
+ * @throws {ResponseError} 404 - Product not found
209
+ * @throws {ResponseError} 400 - Invalid ID format
210
+ */
211
+ public async delete(id: string): Promise<ProductDto> {
212
+ const productId = parseId(id);
213
+ const index = products.findIndex((product) => product.id === productId);
214
+ if (index == -1) throw new ResponseError("Product dose not exist.", 404);
215
+ return products.splice(index, 1)[0];
216
+ }
217
+ }
@@ -1,9 +1,23 @@
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
- email: z.string().email(),
14
+ email: z.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
+ export const ZodUserDto = ZodUser.omit({ password: true });
20
+
21
+ export type User = z.infer<typeof ZodUser>;
22
+ export type UserCreationDto = z.infer<typeof ZodUserCreationDto>;
23
+ export type UserDto = z.infer<typeof ZodUserDto>;
@@ -0,0 +1,96 @@
1
+ import { Router, Request, Response } from "express";
2
+ import asyncHandler from "express-async-handler";
3
+ import UserController from "./user.controller";
4
+
5
+ const router = Router();
6
+ const userController = new UserController();
7
+
8
+ /**
9
+ * @swagger
10
+ * /users:
11
+ * post:
12
+ * summary: Create a user
13
+ * description: Create a new user.
14
+ * consumes:
15
+ * - application/json
16
+ * produces:
17
+ * - application/json
18
+ * parameters:
19
+ * - in: body
20
+ * name: user
21
+ * required: true
22
+ * schema:
23
+ * type: object
24
+ * required:
25
+ * - username
26
+ * - email
27
+ * - password
28
+ * properties:
29
+ * username:
30
+ * type: string
31
+ * minLength: 3
32
+ * example: JessicaSmith
33
+ * email:
34
+ * type: string
35
+ * format: email
36
+ * example: user@example.com
37
+ * password:
38
+ * type: string
39
+ * minLength: 6
40
+ * example: securePassword123
41
+ * responses:
42
+ * 201:
43
+ * description: User created
44
+ */
45
+ router.post(
46
+ "/",
47
+ asyncHandler(async (req: Request, res: Response) => {
48
+ const user = await userController.create(req.body);
49
+ res.status(201).json(user);
50
+ })
51
+ );
52
+
53
+ /**
54
+ * @swagger
55
+ * /users/{id}:
56
+ * get:
57
+ * summary: Get a user by ID
58
+ * parameters:
59
+ * - name: id
60
+ * in: path
61
+ * required: true
62
+ * description: The ID of the product
63
+ * schema:
64
+ * type: integer
65
+ * responses:
66
+ * 200:
67
+ * description: A user object
68
+ * 404:
69
+ * description: user not found
70
+ */
71
+ router.get(
72
+ "/:id",
73
+ asyncHandler(async (req: Request, res: Response) => {
74
+ const user = await userController.get(req.params.id);
75
+ res.json(user);
76
+ })
77
+ );
78
+
79
+ /**
80
+ * @swagger
81
+ * /users:
82
+ * get:
83
+ * summary: Get users
84
+ * description: Returns a list of user objects.
85
+ * responses:
86
+ * 200:
87
+ * description: A list of user objects
88
+ */
89
+ router.get(
90
+ "/",
91
+ asyncHandler(async (req: Request, res: Response) => {
92
+ res.json(await userController.getAll());
93
+ })
94
+ );
95
+
96
+ export = router;
@@ -1,33 +1,153 @@
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 {
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";
12
+
13
+ // Array to store users (as a mock database)
14
+ const users = [
15
+ {
16
+ id: 1,
17
+ username: "johndoe",
18
+ email: "johndoe@gmail.com",
19
+ password: "S3cur3P@ssw0rd",
20
+ },
21
+ {
22
+ id: 2,
23
+ username: "janesmith",
24
+ email: "janesmith@yahoo.com",
25
+ password: "P@ssw0rd2024",
26
+ },
27
+ {
28
+ id: 3,
29
+ username: "michael89",
30
+ email: "michael89@hotmail.com",
31
+ password: "M1chael!2024",
32
+ },
33
+ {
34
+ id: 4,
35
+ username: "lisa.wong",
36
+ email: "lisa.wong@example.com",
37
+ password: "L1saW0ng!2024",
38
+ },
39
+ {
40
+ id: 5,
41
+ username: "alex_k",
42
+ email: "alex.k@gmail.com",
43
+ password: "A1ex#Key2024",
44
+ },
45
+ {
46
+ id: 6,
47
+ username: "emilyj",
48
+ email: "emilyj@hotmail.com",
49
+ password: "Em!ly0101",
50
+ },
51
+ {
52
+ id: 7,
53
+ username: "davidparker",
54
+ email: "david.parker@yahoo.com",
55
+ password: "D@v!d2024",
56
+ },
57
+ {
58
+ id: 8,
59
+ username: "sophia_m",
60
+ email: "sophia.m@gmail.com",
61
+ password: "Sophi@2024",
62
+ },
63
+ {
64
+ id: 9,
65
+ username: "chrisw",
66
+ email: "chrisw@outlook.com",
67
+ password: "Chri$Wong21",
68
+ },
69
+ {
70
+ id: 10,
71
+ username: "natalie_b",
72
+ email: "natalie_b@gmail.com",
73
+ password: "N@talie#B2024",
74
+ },
75
+ ];
4
76
 
5
77
  function exceedHandler() {
6
- throw new Error("Too much call in allowed window");
78
+ const message = "Too much call in allowed window";
79
+ throw new ResponseError(message, 429);
7
80
  }
8
81
 
9
- function errorHandler(e: Error): void {
10
- console.error(e);
82
+ function getUserErrorHandler(e: Error) {
83
+ const message = "User not found.";
84
+ throw new ResponseError(message, 404, e.message);
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
- timeSpanMs: 60000,
19
- allowedCalls: 300,
96
+ timeSpanMs: config.rateLimitTimeSpan,
97
+ allowedCalls: config.rateLimitAllowedCalls,
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 });
110
+ }
111
+
112
+ @memoizeAsync(config.memoizeTime)
22
113
  @onError({
23
- func: errorHandler,
114
+ func: getUserErrorHandler,
24
115
  })
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";
116
+ @rateLimit({
117
+ timeSpanMs: config.rateLimitTimeSpan,
118
+ allowedCalls: config.rateLimitAllowedCalls,
119
+ exceedHandler,
120
+ })
121
+ /**
122
+ * Get user by ID
123
+ * @param {string} id - User ID as string
124
+ * @returns {Promise<User>} User details or error object
125
+ * @throws {ResponseError} 404 - User not found
126
+ * @throws {ResponseError} 400 - Invalid ID format
127
+ */
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);
132
+ return user satisfies User;
133
+ }
134
+
135
+ @memoizeAsync(config.memoizeTime)
136
+ @timeout(config.timeout)
137
+ @rateLimit({
138
+ timeSpanMs: config.rateLimitTimeSpan,
139
+ allowedCalls: config.rateLimitAllowedCalls,
140
+ exceedHandler,
141
+ })
142
+ /**
143
+ * Get all users with masked passwords
144
+ * @returns {Promise<User[]>} List of users with hidden password fields
145
+ * @throws {ResponseError} 500 - When rate limit exceeded
146
+ */
147
+ public async getAll(): Promise<UserDto[]> {
148
+ return users.map((user) => ({
149
+ ...user,
150
+ password: "?",
151
+ }));
32
152
  }
33
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
+ }