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.
@@ -0,0 +1,3 @@
1
+ {
2
+ "cSpell.words": ["microframework"]
3
+ }
package/README.md CHANGED
@@ -53,34 +53,36 @@ To get started with the project, follow these steps:
53
53
 
54
54
  **/src**
55
55
  ├── **/routers** `Directory containing all router files`
56
- │ ├── **/user** `Routers for user-related endpoints`
57
- │ │ ├── user_router1.ts `/user/user_router1`
58
- │ │ ├── user_router2.ts `/user/user_router2`
56
+ │ ├── **/users** `Routers for user-related endpoints`
57
+ │ │ ├── index.router.ts `/users`
58
+ │ │ ├── user-router2.router.ts `/users/user-router2`
59
59
  │ │ ├── user.controller.ts
60
60
  │ │ ├── user.service.ts
61
61
  │ │ └── user.dto.ts
62
- │ ├── **/product** `Routers for product-related endpoints`
63
- │ │ ├── index.ts `/product`
64
- │ │ ├── product_test.spec.ts
62
+ │ ├── **/products** `Routers for product-related endpoints`
63
+ │ │ ├── index.router.ts `/products`
65
64
  │ │ ├── product.controller.ts
66
65
  │ │ ├── product.service.ts
67
- │ │ ├── **/dto**
66
+ │ │ ├── **/dtos**
68
67
  │ │ │ └── product.dto.ts
69
- ├── **/order** `Routers for order-related endpoints`
70
- ├── index.ts `/order`
71
- │ │ ├── order_test.spec.ts
68
+ | | └── product-types.dto.ts
69
+ │ ├── **/orders** `Routers for order-related endpoints`
70
+ │ │ ├── index.router.ts `/orders`
72
71
  │ │ ├── order.controller.ts
73
- │ │ └── order.service.ts
74
- └── index.ts `Home page`
72
+ │ │ ├── order.controller.spec.ts
73
+ ├── order.service.ts
74
+ │ │ └── order.service.spec.ts
75
+ │ └── index.router.ts `Home page`
76
+ │ └── app.controller.ts `Home page`
75
77
  ├── app.ts `Main application file`
76
78
  ├── config.ts `Application configuration file`
77
79
  └── ... `Other files (middleware, models, etc.)`
78
80
 
79
81
  ### Router Directory
80
82
 
81
- Each router file in the `/routers` directory is organized to handle related endpoints. The `app.ts` file automatically imports all routers and mounts them to the main Express application, making it simple to add new routes without modifying central files.
83
+ Each router file in the `/routers` directory is organized to handle related endpoints. The `app.ts` file automatically imports all routers and mounts them on the main Express application, making it straightforward to add new routes without touching central code.
82
84
 
83
- Files that end with `.controller`, `.service`, `.spec`, `.dto`, `.middleware`, `.error`, `.class`, or `.decorator`, as well as those that start with `_` or `@`, are excluded from the router list and can be utilized for various other purposes within the application.
85
+ Files ending with `.router.ts` or `.router.js` are automatically included in the router list and can be reused for various purposes within the application.
84
86
 
85
87
  Example of a basic router:
86
88
 
@@ -88,7 +90,6 @@ Example of a basic router:
88
90
  import { Router, Request, Response } from "express";
89
91
  import asyncHandler from "express-async-handler";
90
92
  import UserController from "./user.controller";
91
- import { sendError } from "@src/utilities";
92
93
 
93
94
  const router = Router();
94
95
  const userController = new UserController();
@@ -160,9 +161,7 @@ router.get(
160
161
  "/:id",
161
162
  asyncHandler(async (req: Request, res: Response) => {
162
163
  const user = await userController.get(req.params.id);
163
-
164
- if ("id" in user == false) sendError(res, user);
165
- else res.json(user);
164
+ res.json(user);
166
165
  })
167
166
  );
168
167
 
@@ -214,14 +213,16 @@ import { z } from "zod";
214
213
  export const ZodUser = z.object({
215
214
  id: z.number().min(1).int(),
216
215
  username: z.string().min(3),
217
- email: z.string().email(),
216
+ email: z.email(),
218
217
  password: z.string().min(6),
219
218
  });
220
219
 
221
220
  export const ZodUserCreationDto = ZodUser.omit({ id: true });
221
+ export const ZodUserDto = ZodUser.omit({ password: true });
222
222
 
223
223
  export type User = z.infer<typeof ZodUser>;
224
224
  export type UserCreationDto = z.infer<typeof ZodUserCreationDto>;
225
+ export type UserDto = z.infer<typeof ZodUserDto>;
225
226
  ```
226
227
 
227
228
  - **Throttling and Rate Limiting**: The `@rateLimit` decorator is applied to safeguard the application's endpoints from abuse by limiting how frequently a particular method can be invoked.
@@ -233,11 +234,17 @@ By using a well-organized controller structure, this project makes it easier to
233
234
  Here is a quick reference to the UserController in practice:
234
235
 
235
236
  ```typescript
236
- import { User, ZodUserCreationDto, UserCreationDto } from "./dto/user.dto";
237
- import { onError, rateLimit, timeout } from "utils-decorators";
238
- import { Validate, ZodInput } from "@pkg/ts-zod-decorators";
239
- import { ResponseError } from "@src/types";
240
- import { tryParseId } from "@src/utilities";
237
+ import {
238
+ User,
239
+ ZodUserCreationDto,
240
+ UserCreationDto,
241
+ UserDto,
242
+ } from "./dto/user.dto";
243
+ import { memoizeAsync, onError, rateLimit, timeout } from "utils-decorators";
244
+ import { Validate, ZodInput } from "ts-zod4-decorators";
245
+ import { ResponseError } from "@/types";
246
+ import { parseId } from "@/utilities/error-handling";
247
+ import config from "@/config";
241
248
 
242
249
  // Array to store users (as a mock database)
243
250
  const users = [
@@ -305,18 +312,12 @@ const users = [
305
312
 
306
313
  function exceedHandler() {
307
314
  const message = "Too much call in allowed window";
308
-
309
- throw new Error(message, {
310
- cause: { status: 500, message } satisfies ResponseError,
311
- });
315
+ throw new ResponseError(message, 429);
312
316
  }
313
317
 
314
318
  function getUserErrorHandler(e: Error) {
315
319
  const message = "User not found.";
316
-
317
- throw new Error(message, {
318
- cause: { status: 404, message, details: e.message } satisfies ResponseError,
319
- });
320
+ throw new ResponseError(message, 404, e.message);
320
321
  }
321
322
 
322
323
  /**
@@ -328,8 +329,8 @@ export default class UserController {
328
329
  // constructor(private readonly userService: UserService) { }
329
330
 
330
331
  @rateLimit({
331
- timeSpanMs: 60000,
332
- allowedCalls: 300,
332
+ timeSpanMs: config.rateLimitTimeSpan,
333
+ allowedCalls: config.rateLimitAllowedCalls,
333
334
  exceedHandler,
334
335
  })
335
336
  @Validate
@@ -341,57 +342,49 @@ export default class UserController {
341
342
  * @throws {ResponseError} 400 - Invalid input data
342
343
  */
343
344
  public async create(@ZodInput(ZodUserCreationDto) user: UserCreationDto) {
344
- users.push({ ...user, id: users.length + 1 } satisfies User);
345
+ users.push({ ...user, id: users.length + 1 });
345
346
  }
346
347
 
348
+ @memoizeAsync(config.memoizeTime)
347
349
  @onError({
348
350
  func: getUserErrorHandler,
349
351
  })
350
352
  @rateLimit({
351
- timeSpanMs: 60000,
352
- allowedCalls: 300,
353
+ timeSpanMs: config.rateLimitTimeSpan,
354
+ allowedCalls: config.rateLimitAllowedCalls,
353
355
  exceedHandler,
354
356
  })
355
357
  /**
356
358
  * Get user by ID
357
359
  * @param {string} id - User ID as string
358
- * @returns {Promise<User | ResponseError>} User details or error object
360
+ * @returns {Promise<User>} User details or error object
359
361
  * @throws {ResponseError} 404 - User not found
360
362
  * @throws {ResponseError} 400 - Invalid ID format
361
363
  */
362
- public async get(id: string): Promise<User | ResponseError> {
363
- const userId = tryParseId(id);
364
- if (typeof userId != "number") return userId satisfies ResponseError;
365
- const user = users.find((user) => user.id === userId);
366
-
367
- if (!user)
368
- return {
369
- status: 404,
370
- message: "User dose not exist.",
371
- } satisfies ResponseError;
372
-
364
+ public async get(id: string): Promise<UserDto> {
365
+ const response = parseId(id);
366
+ const user = users.find((user) => user.id === response);
367
+ if (user == null) throw new ResponseError("User dose not exist.", 404);
373
368
  return user satisfies User;
374
369
  }
375
370
 
376
- @timeout(20000)
371
+ @memoizeAsync(config.memoizeTime)
372
+ @timeout(config.timeout)
377
373
  @rateLimit({
378
- timeSpanMs: 60000,
379
- allowedCalls: 300,
374
+ timeSpanMs: config.rateLimitTimeSpan,
375
+ allowedCalls: config.rateLimitAllowedCalls,
380
376
  exceedHandler,
381
377
  })
382
378
  /**
383
379
  * Get all users with masked passwords
384
- * @returns {Promise<User[]>} List of users with hidden password fields
380
+ * @returns {Promise<UserDto[]>} List of users with hidden password fields
385
381
  * @throws {ResponseError} 500 - When rate limit exceeded
386
382
  */
387
- public async getAll(): Promise<User[]> {
388
- return users.map(
389
- (user) =>
390
- ({
391
- ...user,
392
- password: "?",
393
- } satisfies User)
394
- );
383
+ public async getAll(): Promise<UserDto[]> {
384
+ return users.map((user) => ({
385
+ ...user,
386
+ password: "?",
387
+ }));
395
388
  }
396
389
  }
397
390
  ```
package/package.json CHANGED
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "name": "codeweaver",
3
- "version": "1.1.0",
4
- "main": "src/app.ts",
3
+ "version": "2.0.0",
4
+ "main": "src/main.ts",
5
5
  "bin": {
6
6
  "create-codeweaver-app": "./command.js"
7
7
  },
8
8
  "scripts": {
9
- "start": "nodemon -r tsconfig-paths/register src/app.ts",
10
- "dev": "nodemon -r tsconfig-paths/register src/app.ts",
9
+ "start": "nodemon -r tsconfig-paths/register src/main.ts",
10
+ "dev": "nodemon -r tsconfig-paths/register src/main.ts",
11
11
  "build": "tsc && tsc-alias && copyfiles -u 1 \"src/**/*.{json,env,sqlite,db,sql,yaml,yml,xml,txt,md}\" dist",
12
- "serve": "cross-env TSCONFIG_PATH=tsconfig.paths.json node -r tsconfig-paths/register dist/app.js"
12
+ "serve": "cross-env TSCONFIG_PATH=tsconfig.paths.json node -r tsconfig-paths/register dist/main.js"
13
13
  },
14
14
  "keywords": [
15
15
  "codeweaver",
@@ -32,8 +32,11 @@
32
32
  "license": "MIT",
33
33
  "description": "A lightweight framework built on top of Express and TypeScript",
34
34
  "dependencies": {
35
+ "dotenv": "^17.2.3",
35
36
  "express": "^5.1.0",
36
37
  "express-async-handler": "^1.2.0",
38
+ "swagger-jsdoc": "^3.7.0",
39
+ "ts-zod4-decorators": "^1.0.0",
37
40
  "utils-decorators": "^2.10.0",
38
41
  "zod": "^4.0.14"
39
42
  },
@@ -43,7 +46,6 @@
43
46
  "copyfiles": "^2.4.1",
44
47
  "cross-env": "^10.0.0",
45
48
  "nodemon": "^3.1.10",
46
- "swagger-jsdoc": "^6.2.8",
47
49
  "swagger-ui-express": "^5.0.1",
48
50
  "ts-node": "^10.9.2",
49
51
  "tsc-alias": "^1.8.16",
package/src/config.ts CHANGED
@@ -1,3 +1,12 @@
1
+ import {
2
+ memoizeTime,
3
+ productionEnvironment,
4
+ rateLimitTimeSpan,
5
+ rateLimitAllowedCalls,
6
+ timeout,
7
+ portNumber,
8
+ } from "./constants";
9
+
1
10
  /**
2
11
  * Server configuration interface
3
12
  * @interface
@@ -53,13 +62,18 @@ interface SwaggerOptions {
53
62
  */
54
63
  interface Config {
55
64
  devMode: boolean;
56
- port: string;
65
+ port: number;
57
66
  swaggerOptions: SwaggerOptions;
67
+ timeout: number;
68
+ rateLimitTimeSpan: number;
69
+ rateLimitAllowedCalls: number;
70
+ memoizeTime: number;
58
71
  }
59
72
 
60
- const port = process.env.PORT || "3000";
61
- const config: Config = {
62
- devMode: process.env.NODE_ENV !== "production",
73
+ const port = Number(process.env.PORT) || portNumber;
74
+
75
+ let config: Config = {
76
+ devMode: process.env.NODE_ENV !== productionEnvironment,
63
77
  port,
64
78
  swaggerOptions: {
65
79
  swaggerDefinition: {
@@ -82,17 +96,11 @@ const config: Config = {
82
96
  "./src/routers/**/*.js",
83
97
  ], // Path to the API docs
84
98
  },
99
+ timeout: Number(process.env.TIMEOUT) || timeout,
100
+ rateLimitTimeSpan: Number(process.env.RATE_LIMIT) || rateLimitTimeSpan,
101
+ rateLimitAllowedCalls:
102
+ Number(process.env.RATE_LIMIT) || rateLimitAllowedCalls,
103
+ memoizeTime: Number(process.env.MEMOIZE_TIME) || memoizeTime,
85
104
  };
86
105
 
87
- // Other configurations:
88
- //
89
- // config.jwt_key = config.devMode ? "" : "";
90
- // config.jwt_expiration = config.devMode ? 360000 : 360000;
91
- // config.dbConnectionString = config.devMode ? `mongoDb url` : `mongoDb url`;
92
- // config.mongoDebug = config.devMode;
93
- // config.port = config.devMode ? 3000 : 3000;
94
- // config.host = config.devMode ? "localhost" : "localhost";
95
- // config.env = config.devMode ? "development" : "production";
96
- // config.mongoUrl = config.devMode ? "mongodb://localhost:27017/test" : "mongodb://localhost:27017/test";
97
-
98
106
  export default config;
@@ -0,0 +1,6 @@
1
+ export const timeout = 20000;
2
+ export const rateLimitTimeSpan = 60000;
3
+ export const rateLimitAllowedCalls = 300;
4
+ export const memoizeTime = 1000 * 60 * 60;
5
+ export const productionEnvironment = "production";
6
+ export const portNumber = 3000;
package/src/main.ts ADDED
@@ -0,0 +1,82 @@
1
+ import express, { NextFunction, Request, Response } from "express";
2
+ import fs from "fs";
3
+ import path from "path";
4
+ import config from "./config";
5
+
6
+ /**
7
+ * Recursively loads Express routers from directory
8
+ * @param {string} routerPath - Directory path to scan
9
+ * @param {string} [basePath=""] - Base route path
10
+ */
11
+ function loadRouters(routerPath: string, basePath: string = "") {
12
+ // Read entries with their type info
13
+ const entries = fs.readdirSync(routerPath, { withFileTypes: true });
14
+
15
+ for (const entry of entries) {
16
+ const fullPath = path.join(routerPath, entry.name);
17
+
18
+ if (entry.isDirectory()) {
19
+ // Recurse into subdirectories
20
+ const subRoutePath = path
21
+ .join(basePath, entry.name)
22
+ .replace(/\\/g, "/")
23
+ .replace(/\/?index$/g, "");
24
+ loadRouters(fullPath, subRoutePath);
25
+ continue;
26
+ }
27
+
28
+ // Only handle router files
29
+ if (
30
+ !entry.name.endsWith(".router.ts") &&
31
+ !entry.name.endsWith(".router.js")
32
+ ) {
33
+ continue;
34
+ }
35
+
36
+ // Build route path safely
37
+ const routePath = path
38
+ .join(basePath, entry.name.replace(/\.router\.[tj]s$/, ""))
39
+ .replace(/\\/g, "/")
40
+ .replace(/\/?index$/g, "");
41
+
42
+ // Optional: skip if the target path would be empty (maps to /)
43
+ const mountPath = "/" + (routePath || "");
44
+
45
+ // Import and mount
46
+ const router = require(fullPath);
47
+ app.use(mountPath, router);
48
+ console.log(`Mounted ${entry.name} at ${mountPath}`);
49
+ }
50
+ }
51
+
52
+ const app = express();
53
+ app.use(express.json());
54
+ app.use(express.urlencoded({ extended: true }));
55
+
56
+ //app.use(cors());
57
+
58
+ // Automatically import all routers from the /src/routers directory
59
+ const routersPath = path.join(__dirname, "/routers");
60
+ loadRouters(routersPath);
61
+
62
+ // Swagger setup
63
+ if (config.devMode) {
64
+ const swaggerJsDoc = require("swagger-jsdoc");
65
+ const swaggerUi = require("swagger-ui-express");
66
+ const swaggerDocs = swaggerJsDoc(config.swaggerOptions);
67
+ app.use("/api-docs", swaggerUi.serve, swaggerUi.setup(swaggerDocs));
68
+ }
69
+
70
+ // General error handler
71
+ app.use((err: unknown, req: Request, res: Response, next: NextFunction) => {
72
+ res.status(500).json(err);
73
+ });
74
+
75
+ // Start the server
76
+ app.listen(config.port, () => {
77
+ console.log(`Server is running on http://localhost:${config.port}`);
78
+ if (config.devMode)
79
+ console.log(
80
+ `Swagger UI is available at http://localhost:${config.port}/api-docs`
81
+ );
82
+ });
@@ -38,3 +38,4 @@ export const ZodOrderCreationDto = ZodOrder.omit({
38
38
 
39
39
  export type Order = z.infer<typeof ZodOrder>;
40
40
  export type OrderCreationDto = z.infer<typeof ZodOrderCreationDto>;
41
+ export type OrderDto = z.infer<typeof ZodOrder>;
@@ -1,7 +1,6 @@
1
1
  import { Router, Request, Response } from "express";
2
2
  import asyncHandler from "express-async-handler";
3
3
  import OrderController from "./order.controller";
4
- import { sendError } from "@/utilities";
5
4
 
6
5
  const router = Router();
7
6
  const orderController = new OrderController();
@@ -101,8 +100,7 @@ router.get(
101
100
  "/:id",
102
101
  asyncHandler(async (req: Request, res: Response) => {
103
102
  const order = await orderController.get(req.params.id);
104
- if ("id" in order == false) sendError(res, order);
105
- else res.json(order);
103
+ res.json(order);
106
104
  })
107
105
  );
108
106
 
@@ -126,8 +124,7 @@ router.patch(
126
124
  "/:id/cancel",
127
125
  asyncHandler(async (req: Request, res: Response) => {
128
126
  const order = await orderController.cancel(req.params.id);
129
- if ("id" in order == false) sendError(res, order);
130
- else res.json(order);
127
+ res.json(order);
131
128
  })
132
129
  );
133
130
 
@@ -153,8 +150,7 @@ router.patch(
153
150
  "/:id/deliver",
154
151
  asyncHandler(async (req: Request, res: Response) => {
155
152
  const order = await orderController.deliver(req.params.id);
156
- if ("id" in order == false) sendError(res, order);
157
- else res.json(order);
153
+ res.json(order);
158
154
  })
159
155
  );
160
156
 
@@ -1,12 +1,14 @@
1
- import { onError, rateLimit, timeout } from "utils-decorators";
1
+ import { memoizeAsync, onError, rateLimit, timeout } from "utils-decorators";
2
2
  import {
3
3
  Order,
4
+ OrderDto,
4
5
  OrderCreationDto,
5
6
  ZodOrderCreationDto,
6
7
  } from "./dto/order.dto";
7
- import { Validate, ZodInput } from "@pkg/ts-zod-decorators";
8
- import { ResponseError } from "@/types";
9
- import { tryParseId } from "@/utilities";
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";
10
12
 
11
13
  // Array to store orders (as a mock database)
12
14
  const orders: Order[] = [
@@ -49,18 +51,12 @@ const orders: Order[] = [
49
51
 
50
52
  function exceedHandler() {
51
53
  const message = "Too much call in allowed window";
52
-
53
- throw new Error(message, {
54
- cause: { status: 500, message } satisfies ResponseError,
55
- });
54
+ throw new ResponseError(message, 429);
56
55
  }
57
56
 
58
57
  function getOrderErrorHandler(e: Error) {
59
58
  const message = "Order not found.";
60
-
61
- throw new Error(message, {
62
- cause: { status: 404, message, details: e.message } satisfies ResponseError,
63
- });
59
+ throw new ResponseError(message, 404, e.message);
64
60
  }
65
61
 
66
62
  /**
@@ -70,8 +66,8 @@ function getOrderErrorHandler(e: Error) {
70
66
  */
71
67
  export default class OrderController {
72
68
  @rateLimit({
73
- timeSpanMs: 60000,
74
- allowedCalls: 300,
69
+ timeSpanMs: config.rateLimitTimeSpan,
70
+ allowedCalls: config.rateLimitAllowedCalls,
75
71
  exceedHandler,
76
72
  })
77
73
  @Validate
@@ -92,23 +88,25 @@ export default class OrderController {
92
88
  return newOrder;
93
89
  }
94
90
 
95
- @timeout(20000)
91
+ @memoizeAsync(config.memoizeTime)
92
+ @timeout(config.timeout)
96
93
  @rateLimit({
97
- timeSpanMs: 60000,
98
- allowedCalls: 300,
94
+ timeSpanMs: config.rateLimitTimeSpan,
95
+ allowedCalls: config.rateLimitAllowedCalls,
99
96
  exceedHandler,
100
97
  })
101
98
  /**
102
99
  * Retrieves all orders
103
100
  * @returns List of orders
104
101
  */
105
- public async getAll(): Promise<Order[]> {
102
+ public async getAll(): Promise<OrderDto[]> {
106
103
  return orders;
107
104
  }
108
105
 
106
+ @memoizeAsync(config.memoizeTime)
109
107
  @rateLimit({
110
- timeSpanMs: 60000,
111
- allowedCalls: 300,
108
+ timeSpanMs: config.rateLimitTimeSpan,
109
+ allowedCalls: config.rateLimitAllowedCalls,
112
110
  exceedHandler,
113
111
  })
114
112
  @onError({
@@ -119,42 +117,33 @@ export default class OrderController {
119
117
  * @param id - Order ID as string
120
118
  * @returns Order details or error object if not found
121
119
  */
122
- public async get(id: string): Promise<Order | ResponseError> {
123
- const orderId = tryParseId(id);
124
- if (typeof orderId != "number") return orderId satisfies ResponseError;
120
+ public async get(id: string): Promise<OrderDto> {
121
+ const orderId = parseId(id);
125
122
  const order = orders.find((order) => order.id === orderId);
126
-
127
- if (!order)
128
- return {
129
- status: 404,
130
- message: "Order dose not exist.",
131
- } satisfies ResponseError;
132
-
123
+ if (order == null) throw new ResponseError("User dose not exist.", 404);
133
124
  return order satisfies Order;
134
125
  }
135
126
 
136
127
  @rateLimit({
137
- timeSpanMs: 60000,
138
- allowedCalls: 300,
128
+ timeSpanMs: config.rateLimitTimeSpan,
129
+ allowedCalls: config.rateLimitAllowedCalls,
139
130
  exceedHandler,
140
131
  })
141
132
  /**
142
133
  * Cancel an existing order
143
134
  * @param {string} id - Order ID to cancel
144
- * @returns {Promise<Order | ResponseError>} Updated order or error object
135
+ * @returns {Promise<Order>} Updated order or error object
145
136
  * @throws {ResponseError} 404 - Order not found
146
137
  * @throws {ResponseError} 400 - Invalid ID format or invalid status for cancellation
147
138
  */
148
- public async cancel(id: string): Promise<Order | ResponseError> {
139
+ public async cancel(id: string): Promise<OrderDto> {
149
140
  let order = await this.get(id);
150
- if ("id" in order == false) return order satisfies ResponseError;
151
-
152
- if (order.status != "Processing")
153
- return {
154
- status: 400,
155
- message:
156
- "Cancellation is not available unless the order is in processing status.",
157
- } satisfies ResponseError;
141
+ if (order.status != "Processing") {
142
+ throw new ResponseError(
143
+ "Cancellation is not available unless the order is in processing status.",
144
+ 400
145
+ );
146
+ }
158
147
 
159
148
  order.status = "Canceled";
160
149
  order.deliveredAt = new Date();
@@ -162,27 +151,25 @@ export default class OrderController {
162
151
  }
163
152
 
164
153
  @rateLimit({
165
- timeSpanMs: 60000,
166
- allowedCalls: 300,
154
+ timeSpanMs: config.rateLimitTimeSpan,
155
+ allowedCalls: config.rateLimitAllowedCalls,
167
156
  exceedHandler,
168
157
  })
169
158
  /**
170
159
  * Mark an order as delivered
171
160
  * @param {string} id - Order ID to mark as delivered
172
- * @returns {Promise<Order | ResponseError>} Updated order or error object
161
+ * @returns {Promise<Order>} Updated order or error object
173
162
  * @throws {ResponseError} 404 - Order not found
174
163
  * @throws {ResponseError} 400 - Invalid ID format or invalid status for delivery
175
164
  */
176
- public async deliver(id: string): Promise<Order | ResponseError> {
165
+ public async deliver(id: string): Promise<OrderDto> {
177
166
  let order = await this.get(id);
178
- if ("id" in order == false) return order satisfies ResponseError;
179
-
180
- if (order.status != "Processing")
181
- return {
182
- status: 400,
183
- message:
184
- "Delivery is only available when the order is in processing status.",
185
- } satisfies ResponseError;
167
+ if (order.status != "Processing") {
168
+ throw new ResponseError(
169
+ "Delivery is only available when the order is in processing status.",
170
+ 400
171
+ );
172
+ }
186
173
 
187
174
  order.status = "Delivered";
188
175
  order.deliveredAt = new Date();
@@ -34,3 +34,4 @@ export const ZodProductUpdateDto = ZodProductCreationDto.partial();
34
34
  export type Product = z.infer<typeof ZodProduct>;
35
35
  export type ProductCreationDto = z.infer<typeof ZodProductCreationDto>;
36
36
  export type ProductUpdateDto = z.infer<typeof ZodProductUpdateDto>;
37
+ export type ProductDto = z.infer<typeof ZodProduct>;