codeweaver 2.3.1 → 3.0.1

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 CHANGED
@@ -31,51 +31,34 @@ To get started with the project, follow these steps:
31
31
  cd my-app
32
32
  ```
33
33
 
34
- 2. **Install dependencies**:
34
+ 2. **Clone the repository**:
35
35
 
36
- Choose your package manager and install:
36
+ Using pnpm:
37
37
 
38
- - Using npm:
39
-
40
- ```bash
41
- npm install
42
- ```
43
-
44
- - Using pnpm:
45
-
46
- ```bash
47
- pnpm install
48
- ```
38
+ ```bash
39
+ pnpm install
40
+ ```
49
41
 
50
- 3. **Run the application**:
42
+ Using npm:
51
43
 
52
44
  ```bash
53
- npm start
45
+ npm install
54
46
  ```
55
47
 
56
- Or, if you used pnpm:
48
+ 3. **Run the application**:
57
49
 
58
50
  ```bash
59
- pnpm start
51
+ npm start
60
52
  ```
61
53
 
62
54
  4. **Visit the Swagger UI**: Open your browser and go to `http://localhost:3000/api-docs` to view the automatically generated API documentation.
63
55
 
64
56
  5. **Build**: Compile the TypeScript files for the production environment:
65
57
 
66
- - Using npm:
67
-
68
- ```bash
69
- npm run build
70
- npm run serve
71
- ```
72
-
73
- - Using pnpm:
74
-
75
- ```bash
76
- pnpm run build
77
- pnpm run serve
78
- ```
58
+ ```bash
59
+ npm run build
60
+ npm run serve
61
+ ```
79
62
 
80
63
  ## Sample Project Structure
81
64
 
package/command.js CHANGED
@@ -1,37 +1,41 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  const fs = require("fs");
4
- const { exec } = require("child_process");
5
4
  const path = require("path");
6
- const { version } = require("os");
7
- const { object } = require("zod");
5
+ const { exec: _exec } = require("child_process");
6
+ const { promisify } = require("util");
8
7
 
9
- // Get the project name from command-line arguments or use a default name
10
- const projectName = process.argv[2] || "my-app";
8
+ const exec = promisify(_exec);
9
+ const readFile = promisify(fs.readFile);
10
+ const writeFile = promisify(fs.writeFile);
11
+ const access = promisify(fs.access);
11
12
 
12
- // Set the URL of the GitHub repository
13
- const repoUrl = "https://github.com/js-code-crafter/codeweaver.git";
13
+ (async () => {
14
+ try {
15
+ // Get the project name from command-line arguments or use a default name
16
+ const projectName = process.argv[2] || "my-app";
14
17
 
15
- // Clone the repository into the specified project name
16
- exec(`git clone ${repoUrl} ${projectName}`, (error) => {
17
- if (error) {
18
- console.error(`Error cloning repository: ${error.message}`);
19
- return;
20
- }
18
+ // Set the URL of the GitHub repository
19
+ const repoUrl = "https://github.com/js-code-crafter/codeweaver.git";
21
20
 
22
- // Path to the cloned package.json file
23
- const packageJsonPath = path.join(projectName, "package.json");
21
+ // Clone the repository into the specified project name
22
+ await exec(`git clone ${repoUrl} ${projectName}`);
23
+ console.log(`Cloned repository into ${projectName}`);
24
24
 
25
- // Update the package.json file
26
- fs.readFile(packageJsonPath, "utf8", (readError, data) => {
27
- if (readError) {
28
- console.error(`Error reading package.json: ${readError.message}`);
29
- return;
30
- }
25
+ // Path to the cloned package.json file
26
+ const packageJsonPath = path.join(projectName, "package.json");
27
+
28
+ // Ensure package.json exists before trying to read
29
+ await access(packageJsonPath);
31
30
 
32
- // Parse the package.json content and update the name
33
- const { bin, ...packageJson } = JSON.parse(data);
31
+ // Read and parse package.json
32
+ const data = await readFile(packageJsonPath, "utf8");
33
+ const parsed = JSON.parse(data);
34
34
 
35
+ // Remove the bin field if it exists (as in your original logic)
36
+ const { bin, ...packageJson } = parsed;
37
+
38
+ // Update fields as requested
35
39
  Object.assign(packageJson, {
36
40
  name: projectName,
37
41
  version: "1.0.0",
@@ -42,35 +46,29 @@ exec(`git clone ${repoUrl} ${projectName}`, (error) => {
42
46
  });
43
47
 
44
48
  // Write the updated package.json back to the file
45
- fs.writeFile(
46
- packageJsonPath,
47
- JSON.stringify(packageJson, null, 2),
48
- (writeError) => {
49
- if (writeError) {
50
- console.error(`Error writing package.json: ${writeError.message}`);
51
- return;
52
- }
53
-
54
- // Remove the command.js file
55
- exec(`rm -f ${path.join(projectName, "command.js")}`, (rmError) => {
56
- if (rmError) {
57
- console.error(`Error removing command.js file: ${rmError.message}`);
58
- return;
59
- }
49
+ await writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2));
60
50
 
61
- // Remove the .git folder
62
- exec(`rm -rf ${path.join(projectName, ".git")}`, (rm2Error) => {
63
- if (rm2Error) {
64
- console.error(`Error removing .git folder: ${rm2Error.message}`);
65
- return;
66
- }
67
-
68
- console.log(
69
- `Successfully cloned codeweaver into ${path.resolve(projectName)}`
70
- );
71
- });
72
- });
51
+ // Remove the command.js file if it exists
52
+ const commandJsPath = path.join(projectName, "command.js");
53
+ try {
54
+ await exec(`rm -f ${commandJsPath}`);
55
+ } catch (err) {
56
+ // If removal fails because the file doesn't exist, ignore
57
+ // Otherwise, log the error
58
+ if (err && err.code !== 1) {
59
+ console.error(`Error removing command.js file: ${err.message}`);
60
+ // Continue anyway
73
61
  }
74
- );
75
- });
76
- });
62
+ }
63
+
64
+ // Remove the .git folder
65
+ try {
66
+ await exec(`rm -rf ${path.join(projectName, ".git")}`);
67
+ } catch (err) {
68
+ console.error(`Error removing .git folder: ${err.message}`);
69
+ // continue anyway
70
+ }
71
+ } catch (error) {
72
+ console.error(`Error: ${error.message}`);
73
+ }
74
+ })();
package/package.json CHANGED
@@ -1,10 +1,7 @@
1
1
  {
2
2
  "name": "codeweaver",
3
- "version": "2.3.1",
3
+ "version": "3.0.1",
4
4
  "main": "src/main.ts",
5
- "bin": {
6
- "create-codeweaver-app": "./command.js"
7
- },
8
5
  "scripts": {
9
6
  "start": "nodemon -r tsconfig-paths/register src/main.ts",
10
7
  "dev": "nodemon -r tsconfig-paths/register src/main.ts",
@@ -12,26 +9,19 @@
12
9
  "serve": "cross-env TSCONFIG_PATH=tsconfig.paths.json node -r tsconfig-paths/register dist/main.js"
13
10
  },
14
11
  "keywords": [
15
- "codeweaver",
16
- "node framework",
17
- "js framework",
18
- "javascript",
19
- "typescript",
20
- "code",
21
- "weaver",
22
12
  "framework",
23
- "boilerplate",
24
- "bootstrap",
25
13
  "lightweight",
26
- "nested routers",
14
+ "node",
15
+ "Express",
16
+ "boilerplate",
17
+ "utility",
27
18
  "decorator",
28
- "swagger",
29
- "zod",
30
- "utils-decorators"
19
+ "controller",
20
+ "microframework"
31
21
  ],
32
- "author": "js-code-crafter",
22
+ "author": "@js-code-crafter - Ehsan Afzali",
33
23
  "license": "MIT",
34
- "description": "A lightweight framework built on top of Express and TypeScript",
24
+ "description": "An unopinionated microframework built with Express, TypeScript, Zod, Swagger",
35
25
  "dependencies": {
36
26
  "cors": "^2.8.5",
37
27
  "dotenv": "^17.2.3",
@@ -1,5 +1,11 @@
1
1
  import { ZodOrder } from "@/entities/order.entity";
2
2
 
3
+ /**
4
+ * DTO for an order.
5
+ * This is derived from the full Order schema.
6
+ */
7
+ export const ZodOrderDto = ZodOrder;
8
+
3
9
  /**
4
10
  * DTO for creating an order.
5
11
  * This is derived from the full Order schema by omitting fields
@@ -127,8 +127,8 @@ router.patch(
127
127
  "/:id/cancel",
128
128
  asyncHandler(async (req: Request, res: Response) => {
129
129
  const id = await orderController.validateId(req.params.id);
130
- const order = await orderController.cancel(id);
131
- res.json(order);
130
+ await orderController.cancel(id);
131
+ res.status(200).send();
132
132
  })
133
133
  );
134
134
 
@@ -154,8 +154,8 @@ router.patch(
154
154
  "/:id/deliver",
155
155
  asyncHandler(async (req: Request, res: Response) => {
156
156
  const id = await orderController.validateId(req.params.id);
157
- const order = await orderController.deliver(id);
158
- res.json(order);
157
+ await orderController.deliver(id);
158
+ res.status(200).send();
159
159
  })
160
160
  );
161
161
 
@@ -1,9 +1,5 @@
1
1
  import { memoizeAsync, onError, rateLimit, timeout } from "utils-decorators";
2
- import {
3
- OrderDto,
4
- OrderCreationDto,
5
- ZodOrderCreationDto,
6
- } from "./dto/order.dto";
2
+ import { OrderDto, OrderCreationDto, ZodOrderDto } from "./dto/order.dto";
7
3
  import { ResponseError } from "@/utilities/error-handling";
8
4
  import { convert, stringToInteger } from "@/utilities/conversion";
9
5
  import config from "@/config";
@@ -11,6 +7,7 @@ import { orders } from "@/db";
11
7
  import { Order, ZodOrder } from "@/entities/order.entity";
12
8
  import { MapAsyncCache } from "@/utilities/cache/memory-cache";
13
9
  import { Injectable } from "@/utilities/container";
10
+ import { parallelMap } from "@/utilities/parallel/parallel";
14
11
 
15
12
  function exceedHandler() {
16
13
  const message = "Too much call in allowed window";
@@ -59,13 +56,14 @@ export default class OrderController {
59
56
  public async validateOrderCreationDto(
60
57
  order: OrderCreationDto
61
58
  ): Promise<Order> {
62
- const newOrder = await ZodOrderCreationDto.parseAsync(order);
63
- return {
64
- ...newOrder,
59
+ const newOrder: Order = {
60
+ ...order,
65
61
  id: orders.length + 1,
66
62
  status: "Processing",
67
63
  createdAt: new Date(),
68
64
  };
65
+
66
+ return convert(newOrder, ZodOrder);
69
67
  }
70
68
 
71
69
  @rateLimit({
@@ -82,7 +80,6 @@ export default class OrderController {
82
80
  */
83
81
  public async create(order: Order): Promise<void> {
84
82
  orders.push(order);
85
- await orderCache.set(order.id.toString(), order as OrderDto);
86
83
  await ordersCache.delete("key");
87
84
  }
88
85
 
@@ -102,7 +99,10 @@ export default class OrderController {
102
99
  * @returns List of orders
103
100
  */
104
101
  public async getAll(): Promise<OrderDto[]> {
105
- return orders as OrderDto[];
102
+ return await parallelMap(
103
+ orders,
104
+ async (order) => await convert(order, ZodOrderDto)
105
+ );
106
106
  }
107
107
 
108
108
  @memoizeAsync({
@@ -125,7 +125,7 @@ export default class OrderController {
125
125
  if (order == null) {
126
126
  throw new ResponseError("Order not found");
127
127
  }
128
- return convert(order!, ZodOrder);
128
+ return await convert(order, ZodOrder);
129
129
  }
130
130
 
131
131
  @rateLimit({
@@ -140,7 +140,7 @@ export default class OrderController {
140
140
  * @throws {ResponseError} 404 - Order not found
141
141
  * @throws {ResponseError} 400 - Invalid ID format or invalid status for cancellation
142
142
  */
143
- public async cancel(id: number): Promise<OrderDto> {
143
+ public async cancel(id: number): Promise<void> {
144
144
  let order = await this.get(id);
145
145
  if (order.status != "Processing") {
146
146
  throw new ResponseError(
@@ -151,9 +151,8 @@ export default class OrderController {
151
151
  order.status = "Canceled";
152
152
  order.deliveredAt = new Date();
153
153
 
154
- await orderCache.set(id.toString(), order);
154
+ await orderCache.delete(id.toString());
155
155
  await ordersCache.delete("key");
156
- return order;
157
156
  }
158
157
 
159
158
  @rateLimit({
@@ -168,7 +167,7 @@ export default class OrderController {
168
167
  * @throws {ResponseError} 404 - Order not found
169
168
  * @throws {ResponseError} 400 - Invalid ID format or invalid status for delivery
170
169
  */
171
- public async deliver(id: number): Promise<OrderDto> {
170
+ public async deliver(id: number): Promise<void> {
172
171
  let order = await this.get(id);
173
172
  if (order.status != "Processing") {
174
173
  throw new ResponseError(
@@ -179,8 +178,7 @@ export default class OrderController {
179
178
  order.status = "Delivered";
180
179
  order.deliveredAt = new Date();
181
180
 
182
- await orderCache.set(id.toString(), order);
181
+ await orderCache.delete(id.toString());
183
182
  await ordersCache.delete("key");
184
- return order;
185
183
  }
186
184
  }
@@ -1,5 +1,10 @@
1
1
  import { ZodProduct } from "@/entities/product.entity";
2
- import z from "zod";
2
+
3
+ /**
4
+ * DTO for a Product.
5
+ * Derived from the full Product schema.
6
+ */
7
+ export const ZodProductDto = ZodProduct;
3
8
 
4
9
  /**
5
10
  * DTO for creating a Product.
@@ -11,7 +16,7 @@ export const ZodProductCreationDto = ZodProduct.omit({ id: true });
11
16
  * DTO for updating a Product.
12
17
  * All fields are optional to support partial updates (PATCH semantics).
13
18
  */
14
- export const ZodProductUpdateDto = ZodProductCreationDto.partial();
19
+ export const ZodProductUpdateDto = ZodProductDto.partial();
15
20
 
16
21
  /**
17
22
  * Product categories supported by the system.
@@ -44,7 +49,7 @@ export type ProductCreationDto = {
44
49
  stock: number;
45
50
 
46
51
  /** Optional product description. */
47
- description?: string | undefined;
52
+ description?: string;
48
53
  };
49
54
 
50
55
  /**
@@ -52,20 +57,23 @@ export type ProductCreationDto = {
52
57
  * All fields are optional to support partial updates.
53
58
  */
54
59
  export type ProductUpdateDto = {
60
+ /** Product ID */
61
+ id: number;
62
+
55
63
  /** Optional product name. */
56
- name?: string | undefined;
64
+ name?: string;
57
65
 
58
66
  /** Optional product price. */
59
- price?: number | undefined;
67
+ price?: number;
60
68
 
61
69
  /** Optional product description. */
62
- description?: string | undefined;
70
+ description?: string;
63
71
 
64
72
  /** Optional product category. Must be one of the predefined categories if provided. */
65
- category?: ProductCategory | undefined;
73
+ category?: ProductCategory;
66
74
 
67
75
  /** Optional stock count in inventory. */
68
- stock?: number | undefined;
76
+ stock?: number;
69
77
  };
70
78
 
71
79
  /**
@@ -88,5 +96,5 @@ export type ProductDto = {
88
96
  stock: number;
89
97
 
90
98
  /** Optional product description. */
91
- description?: string | undefined;
99
+ description?: string;
92
100
  };
@@ -170,8 +170,9 @@ router.put(
170
170
  "/:id",
171
171
  asyncHandler(async (req: Request, res: Response) => {
172
172
  const id = await productController.validateId(req.params.id);
173
- const product = await productController.update(id, req.body);
174
- res.json(product);
173
+ const product = await productController.validateProductUpdateDto(req.body);
174
+ await productController.update(id, product);
175
+ res.status(200).send();
175
176
  })
176
177
  );
177
178
 
@@ -199,8 +200,7 @@ router.delete(
199
200
  "/:id",
200
201
  asyncHandler(async (req: Request, res: Response) => {
201
202
  const id = await productController.validateId(req.params.id);
202
- const product = await productController.delete(id);
203
- res.json(product);
203
+ await productController.delete(id);
204
204
  })
205
205
  );
206
206
 
@@ -1,16 +1,9 @@
1
- import {
2
- memoizeAsync,
3
- onError,
4
- rateLimit,
5
- timeout,
6
- before,
7
- } from "utils-decorators";
1
+ import { memoizeAsync, onError, rateLimit, timeout } from "utils-decorators";
8
2
  import {
9
3
  ProductCreationDto,
10
4
  ProductDto,
11
5
  ProductUpdateDto,
12
- ZodProductCreationDto,
13
- ZodProductUpdateDto,
6
+ ZodProductDto,
14
7
  } from "./dto/product.dto";
15
8
  import { MapAsyncCache } from "@/utilities/cache/memory-cache";
16
9
  import { convert, stringToInteger } from "@/utilities/conversion";
@@ -19,6 +12,8 @@ import { ResponseError } from "@/utilities/error-handling";
19
12
  import { products } from "@/db";
20
13
  import { Product, ZodProduct } from "@/entities/product.entity";
21
14
  import { Injectable } from "@/utilities/container";
15
+ import assign from "@/utilities/assignment";
16
+ import { parallelMap } from "@/utilities/parallel/parallel";
22
17
 
23
18
  function exceedHandler() {
24
19
  const message = "Too much call in allowed window";
@@ -67,8 +62,7 @@ export default class ProductController {
67
62
  public async validateProductCreationDto(
68
63
  product: ProductCreationDto
69
64
  ): Promise<Product> {
70
- const newProduct = await ZodProductCreationDto.parseAsync(product);
71
- return { ...newProduct, id: products.length + 1 };
65
+ return await convert({ ...product, id: products.length + 1 }, ZodProduct);
72
66
  }
73
67
 
74
68
  @onError({
@@ -81,11 +75,9 @@ export default class ProductController {
81
75
  * @returns {Product} A fully formed Product object ready for persistence.
82
76
  */
83
77
  public async validateProductUpdateDto(
84
- product: ProductCreationDto
78
+ product: ProductUpdateDto
85
79
  ): Promise<Product> {
86
- const productDto = await ZodProductUpdateDto.parseAsync(product);
87
- let updatedProduct: Product = convert(productDto, ZodProduct);
88
- return { ...updatedProduct, id: products.length + 1 };
80
+ return await convert(product, ZodProduct);
89
81
  }
90
82
 
91
83
  @rateLimit({
@@ -101,13 +93,7 @@ export default class ProductController {
101
93
  * @throws {ResponseError} 400 - Invalid input data
102
94
  */
103
95
  public async create(product: Product): Promise<void> {
104
- const newProduct: Product = {
105
- ...product,
106
- id: products.length + 1,
107
- };
108
-
109
- products.push(newProduct);
110
- await productCache.set(newProduct.id.toString(), newProduct as ProductDto);
96
+ products.push(product);
111
97
  await productsCache.delete("key");
112
98
  }
113
99
 
@@ -127,7 +113,10 @@ export default class ProductController {
127
113
  * @returns List of products with summarized descriptions
128
114
  */
129
115
  public async getAll(): Promise<ProductDto[]> {
130
- return products as ProductDto[];
116
+ return await parallelMap(
117
+ products,
118
+ async (product) => await convert(product, ZodProductDto)
119
+ );
131
120
  }
132
121
 
133
122
  @memoizeAsync({
@@ -150,7 +139,7 @@ export default class ProductController {
150
139
  if (product == null) {
151
140
  throw new ResponseError("Product not found");
152
141
  }
153
- return convert(product!, ZodProduct);
142
+ return await convert(product, ZodProduct);
154
143
  }
155
144
 
156
145
  @rateLimit({
@@ -166,20 +155,18 @@ export default class ProductController {
166
155
  * @throws {ResponseError} 404 - Product not found
167
156
  * @throws {ResponseError} 400 - Invalid ID format or update data
168
157
  */
169
- public async update(
170
- id: number,
171
- updateData: ProductUpdateDto
172
- ): Promise<ProductDto> {
158
+ public async update(id: number, updateData: Product): Promise<void> {
159
+ if (id != updateData.id) {
160
+ throw new ResponseError("Product ID is immutable.", 400);
161
+ }
173
162
  const product = await this.get(id);
174
163
  if (product != null) {
175
- Object.assign(product, updateData);
176
- await productCache.set(id.toString(), product);
164
+ await assign(updateData, product, ZodProduct);
165
+ await productCache.delete(updateData.id.toString());
177
166
  await productsCache.delete("key");
178
167
  } else {
179
168
  throw new ResponseError("Product dose not exist.", 404);
180
169
  }
181
-
182
- return product;
183
170
  }
184
171
 
185
172
  @rateLimit({
@@ -194,13 +181,13 @@ export default class ProductController {
194
181
  * @throws {ResponseError} 404 - Product not found
195
182
  * @throws {ResponseError} 400 - Invalid ID format
196
183
  */
197
- public async delete(id: number): Promise<ProductDto> {
184
+ public async delete(id: number): Promise<void> {
198
185
  const index = products.findIndex((product) => product.id === id);
199
186
  if (index == -1) {
200
187
  throw new ResponseError("Product dose not exist.", 404);
201
188
  }
202
189
  await productCache.delete(id.toString());
203
190
  await productsCache.delete("key");
204
- return products.splice(index, 1)[0];
191
+ products.splice(index, 1)[0];
205
192
  }
206
193
  }
@@ -1,17 +1,13 @@
1
- import {
2
- ZodUserCreationDto,
3
- UserCreationDto,
4
- UserDto,
5
- ZodUserDto,
6
- } from "./dto/user.dto";
1
+ import { UserCreationDto, UserDto, ZodUserDto } from "./dto/user.dto";
7
2
  import { memoizeAsync, onError, rateLimit, timeout } from "utils-decorators";
8
3
  import { ResponseError } from "@/utilities/error-handling";
9
4
  import { convert, stringToInteger } from "@/utilities/conversion";
10
5
  import config from "@/config";
11
6
  import { users } from "@/db";
12
- import { User } from "@/entities/user.entity";
7
+ import { User, ZodUser } from "@/entities/user.entity";
13
8
  import { MapAsyncCache } from "@/utilities/cache/memory-cache";
14
9
  import { Injectable } from "@/utilities/container";
10
+ import { parallelMap } from "@/utilities/parallel/parallel";
15
11
 
16
12
  function exceedHandler() {
17
13
  const message = "Too much call in allowed window";
@@ -58,8 +54,7 @@ export default class UserController {
58
54
  * @returns {User} A fully formed User object ready for persistence.
59
55
  */
60
56
  public async validateUserCreationDto(user: UserCreationDto): Promise<User> {
61
- const newUser = await ZodUserCreationDto.parseAsync(user);
62
- return { ...newUser, id: users.length + 1 };
57
+ return await convert(user, ZodUser);
63
58
  }
64
59
 
65
60
  @rateLimit({
@@ -76,7 +71,6 @@ export default class UserController {
76
71
  */
77
72
  public async create(user: User): Promise<void> {
78
73
  users.push(user);
79
- await userCache.set(user.id.toString(), user as User);
80
74
  await usersCache.delete("key");
81
75
  }
82
76
 
@@ -97,7 +91,10 @@ export default class UserController {
97
91
  * @throws {ResponseError} 500 - When rate limit exceeded
98
92
  */
99
93
  public async getAll(): Promise<UserDto[]> {
100
- return users as UserDto[];
94
+ return await parallelMap(
95
+ users,
96
+ async (user) => await convert(user, ZodUserDto)
97
+ );
101
98
  }
102
99
 
103
100
  @memoizeAsync({
@@ -122,6 +119,6 @@ export default class UserController {
122
119
  if (user == null) {
123
120
  throw new ResponseError("Product not found");
124
121
  }
125
- return convert(user!, ZodUserDto);
122
+ return convert(user, ZodUserDto);
126
123
  }
127
124
  }