beech-api 3.9.0-beta.8-rc → 3.9.0-beta.9-rc

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
@@ -17,13 +17,13 @@ The Beech API is API framework, It's help you with very easy to create API proje
17
17
  - <b>[Beech CLI tool available](#beech-cli-tool-available)</b>
18
18
  - ✨ <b>Automation Endpoints with CRUD</b>
19
19
  - [Database Connection](#database-connection)
20
- - [Model](#model)
20
+ - [Model](#models)
21
21
  - [Retrieving data with Query String](#retrieving-data-with-query-string)
22
22
  - Conditions
23
23
  - Grouping
24
24
  - Ordering
25
25
  - Timestamps (Add-on in Store and Update)
26
- - [Transactions](#transactions)
26
+ - [Transactions](#-transactions)
27
27
  - [Disorganized transactions](#way-1---disorganized-transactions-)
28
28
  - [Organized transactions](#way-2---organized-transactions-)
29
29
  - [Transactions set Isolation levels](#way-3---transactions-set-isolation-levels-)
@@ -80,8 +80,11 @@ $ yarn global add beech-api
80
80
 
81
81
  Installation demo:
82
82
 
83
- [Demo](https://i.ibb.co/hySFxy3/install-beech720-1.gif)
84
- ![Alt Text](https://i.ibb.co/hySFxy3/install-beech720-1.gif)
83
+ [Demo 1](https://i.ibb.co/TBjNgVrF/1-11.gif)
84
+ ![Alt Text](https://i.ibb.co/TBjNgVrF/1-11.gif)
85
+
86
+ [Demo 2](https://i.ibb.co/wrdgyzDP/ezgif-8539024298ec62a9.gif)
87
+ ![Alt Text](https://i.ibb.co/wrdgyzDP/ezgif-8539024298ec62a9.gif)
85
88
 
86
89
  After installation, you will have access to the `beech-app` binary in your command line.
87
90
  You can check you have the right version with this command:
@@ -179,7 +182,7 @@ Output: FjgcgJPylkV7EeQJjea_EeifPwaHVO9onD3ATk3YYAyvjtMGu3dcDS0ejA
179
182
 
180
183
  📂 app.config.js
181
184
  ```js
182
- // basic & sequelize (needed Hash)
185
+ // Database configuration Only Basic & Sequelize (needed Hash)
183
186
 
184
187
  ...
185
188
 
@@ -217,6 +220,8 @@ database_config: [
217
220
  ```
218
221
  ❓ **Caution! :** Every re-new generate `app_key`. Must to new Hash your Access and change to ALL Database connections.
219
222
 
223
+ ❓ **Development** : You can add ```dev: true``` in ```main_config``` for a better experience.
224
+
220
225
  # Models
221
226
 
222
227
  The `models` keep the files of function(s) data managemnets for Retriving, Creating, Updating and Destroying (CRUD). for understanding you might make model name same your table name inside `src/models` folder.
@@ -325,8 +330,7 @@ Fruit.options = {
325
330
  // Option 2: Allow with specific per methods
326
331
  defaultEndpoint: {
327
332
  GET: true,
328
- POST: false,
329
- PATCH: {
333
+ POST: {
330
334
  allow: true, // allow Auto-Endpoint
331
335
  jwt: {
332
336
  allow: true, // allow JWT
@@ -334,7 +338,19 @@ Fruit.options = {
334
338
  { role: [1, 2] },
335
339
  ],
336
340
  },
341
+ // Rate Limit for POST
342
+ rate_limit: {
343
+ windowMs: 15 * 60 * 1000, // 15 minutes
344
+ limit: 100, // Limit each IP to 100 requests per `window` (here, per 15 minutes).
345
+ // store: ... , // Redis, Memcached, etc.
346
+ // See more: https://www.npmjs.com/package/express-rate-limit#Configuration
347
+ },
348
+ // Duplicate Request for POST
349
+ duplicate_request: {
350
+ expiration: 500, // Can't duplicate request for 5 milliseconds each IP requests per `window` (Disabled for Set expiration to 0 zero.)
351
+ },
337
352
  },
353
+ PATCH: false,
338
354
  DELETE: false,
339
355
  },
340
356
 
@@ -379,16 +395,17 @@ Now you can add Query String with Conditional, Grouping and Ordering (Now Suppor
379
395
 
380
396
  <b style="font-size:12pt">For Example, Now!</b>, You can request to `/fruit` with methods GET, POST, PATCH and DELETE like this.
381
397
 
382
- | Efficacy | Method | Endpoint | Body |
383
- |:---------|:---------|:-----------------------|:-----------|
384
- | Create | POST | /fruit | { } |
385
- | Read | GET | /fruit | No |
386
- | Read | GET | /fruit/:limit/:offset | No |
387
- | Read | GET | /fruit?someField=1 | No |
388
- | Read | GET | /fruit?orderby=sort | No |
389
- | Read | GET | /fruit?groupby=id | No |
390
- | Update | PATCH | /fruit/:id | { } |
391
- | Delete | DELETE | /fruit/:id | No |
398
+ | Efficacy | Method | Endpoint | Body | Mode |
399
+ |:---------|:---------|:-----------------------|:-------------|:--------|
400
+ | Create | POST | /fruit | { } | Single |
401
+ | Create | POST | /fruit | [ ] | Bulk |
402
+ | Read | GET | /fruit | No | Fetch |
403
+ | Read | GET | /fruit/:limit/:offset | No | Fetch |
404
+ | Read | GET | /fruit?someField=1 | No | Fetch |
405
+ | Read | GET | /fruit?orderby=sort | No | Fetch |
406
+ | Read | GET | /fruit?groupby=id | No | Fetch |
407
+ | Update | PATCH | /fruit/:id | { } | Single |
408
+ | Delete | DELETE | /fruit/:id | No | Single |
392
409
 
393
410
  Add some Basic Conditions, Grouping and Ordering with `QUERY STRING` under GET methods<br/>
394
411
 
@@ -627,15 +644,15 @@ The **JWT Broken Role** mechanism provides a flexible way to bypass or override
627
644
 
628
645
  #### It can be applied in three different levels, depending on your use case.
629
646
 
630
- | Level | Scope | Best For |
631
- |:--------------------------------|:------------------------|:--------------------------------|
632
- | Global (``passport.config.js``) | All endpoints | Common authorization exceptions |
633
- | Model-level options | Per model & HTTP method | Structured, reusable rules |
634
- | Endpoint middleware | Single endpoint | Custom or special cases |
647
+ | Priority | Level | Scope | Best For |
648
+ |:---------|:--------------------------------|:-------------------------|:--------------------------------|
649
+ | 3rd | Global (``passport.config.js``) | All endpoints | Common authorization exceptions |
650
+ | 2nd | Model-level options | Per model & HTTP method | Structured, reusable rules |
651
+ | 1st | Endpoint middleware | Single endpoint | Custom or special cases |
635
652
 
636
653
  This multi-layer approach allows you to design **secure, flexible, and maintainable JWT authorization flows**.
637
654
 
638
- ### 1. Global Configuration (passport.config.js)
655
+ ### 1. Global Configuration (passport.config.js) - Priority 3
639
656
 
640
657
  You can define global JWT Broken Role rules that apply to all endpoints by configuring them in ``passport.config.js``.
641
658
  ```js
@@ -667,7 +684,7 @@ module.exports = {
667
684
  - Apply common authorization exceptions across the entire application
668
685
  - Support both simple role arrays and advanced operators (``$in``, ``$regex``, etc.)
669
686
 
670
- ### 2. Model-Level Configuration (Per HTTP Method)
687
+ ### 2. Model-Level Configuration (Per HTTP Method) - Priority 2
671
688
 
672
689
  You can configure JWT and Broken Role rules per model and per HTTP method using model options.
673
690
 
@@ -714,7 +731,7 @@ Fruit.options = {
714
731
  - Different role requirements for POST, PATCH, etc.
715
732
  - Combine role-based and attribute-based conditions
716
733
 
717
- ### 3. Endpoint-Level Configuration (Credentials Middleware)
734
+ ### 3. Endpoint-Level Configuration (Credentials Middleware) - Priority 1
718
735
 
719
736
  For maximum flexibility, you can define Broken Role rules directly on a specific endpoint using the Credentials middleware.
720
737
 
@@ -782,8 +799,8 @@ Credentials([
782
799
  // Common Matching Patterns
783
800
  { role: [1, 2, 3] } // IN
784
801
  { role: 1 } // Equal
785
- { email: 'a@b.com' }
786
- { department: 'IT' }
802
+ { email: 'a@b.com' } // Equal
803
+ { department: 'IT' } // Equal
787
804
  { status: ['active'] } // Dynamic key
788
805
  ```
789
806
 
@@ -959,8 +976,9 @@ module.exports = {
959
976
  When you config passport with ```users``` table already. You will got Auth endpoint in available.
960
977
  ```js
961
978
  POST: "/authentication" // Request token
979
+ POST: "/authentication/refresh" // Request refresh token
962
980
  POST: "/authentication/create" // Create new Auth data
963
- PATCH: "/authentication/update/:id" // Update old Auth data (needed id)
981
+ PATCH: "/authentication/update/:id" // Update old Auth data (needed user id)
964
982
  ```
965
983
 
966
984
  ***XHR Example :***
@@ -968,26 +986,28 @@ PATCH: "/authentication/update/:id" // Update old Auth data (needed id)
968
986
  ```js
969
987
  // Request with body for gether Token
970
988
  POST: "/authentication"
971
- {
989
+ payload: {
972
990
  username: "bombkiml",
973
991
  password: "secret"
974
992
  }
975
993
 
994
+ // Request with header for refresh token
995
+ POST: "/authentication/refresh"
996
+ headers: Authorization: Bearer <your_token>
976
997
 
977
998
  // Request with body for Create Auth data
978
999
  POST: "/authentication/create"
979
- {
1000
+ payload: {
980
1001
  username: "add_new_username",
981
1002
  password: "add_new_secret",
982
1003
  name: "add_new_my_name",
983
1004
  email: "add_new_email"
984
1005
  }
985
1006
 
986
-
987
1007
  // Request with body for Update Auth data
988
1008
  PATCH: "/authentication/update/1"
989
1009
  headers: Authorization: Bearer <your_token>
990
- {
1010
+ payload: {
991
1011
  username: "update_bombkiml",
992
1012
  password: "update_secret",
993
1013
  name: "update_my_name",
@@ -1293,6 +1313,23 @@ module.exports = {
1293
1313
  duplicateRequest: {
1294
1314
  expiration: 500, // Can't duplicate request for 5 milliseconds each IP requests per `window`
1295
1315
  },
1316
+
1317
+ payload: {
1318
+ // The limit of request body size, json default "100KB", urlencoded default "100KB". Learn more: https://expressjs.com/en/4x/api.html#express.json
1319
+ json: {
1320
+ limit: "100KB", // default: "100KB"
1321
+ },
1322
+ urlencoded: {
1323
+ limit: "100KB", // default: "100KB"
1324
+ extended: true, // default: true (reccomended: true)
1325
+ },
1326
+ file: {
1327
+ uploadAllowMethod: ["POST", "PATCH", "PUT"], // Only apply file upload limit for POST, PATCH, PUT method.
1328
+ allowedTypes: ["image/jpeg", "image/png", "application/pdf"], // Example: Allow only JPEG, PNG, and PDF files. Learn more: https://www.digipres.org/formats/mime-types/
1329
+ limit: 5 * 1024 * 1024, // 5MB (default: Infinity)
1330
+ },
1331
+ },
1332
+
1296
1333
  },
1297
1334
  },
1298
1335
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "beech-api",
3
- "version": "3.9.0-beta.8-rc",
3
+ "version": "3.9.0-beta.9-rc",
4
4
  "description": "Command line interface for rapid Beech API development",
5
5
  "keywords": [
6
6
  "api",
@@ -38,6 +38,7 @@
38
38
  "compression": "^1.7.5",
39
39
  "cookie-parser": "^1.4.7",
40
40
  "cors": "^2.8.1",
41
+ "cron": "^4.4.0",
41
42
  "crypto-js": "^4.2.0",
42
43
  "cryptr": "^6.3.0",
43
44
  "express": "^4.20.0",
@@ -72,7 +73,7 @@
72
73
  "walk": "^2.3.14"
73
74
  },
74
75
  "devDependencies": {
75
- "jest": "^29.7.0",
76
+ "jest": "^30.2.0",
76
77
  "nodemon": "^3.1.0",
77
78
  "sequelize-cli": "^5.5.1"
78
79
  },
@@ -231,7 +231,7 @@ class Beech {
231
231
  successfully() {
232
232
  clear();
233
233
  console.log("Beech CLI v" + require(__dirname + "/../../../package.json").version);
234
- console.log('\n Passed  The project has been successfully created.\n\n $ cd ' + this.argument + '\n $ npm start or yarn start');
234
+ console.log('\n Passed  The project has been successfully created.\n\n $ cd ' + this.argument + '\n $ npm run dev or yarn dev');
235
235
  }
236
236
 
237
237
  async contentReplace(pathFile, textCondition) {
@@ -48,13 +48,13 @@ class Beech {
48
48
  setTimeout(() => {
49
49
  clearInterval(refreshCompileIntervalId);
50
50
  logUpdate("\n[OK] Running...");
51
- }, 2500);
51
+ }, 1500);
52
52
  } else {
53
53
  setTimeout(() => {
54
54
  clearInterval(refreshCompileIntervalId);
55
55
  logUpdate("\n[ERR] Failed... ", err);
56
56
  reject();
57
- }, 2500);
57
+ }, 1000);
58
58
  }
59
59
  });
60
60
  }).on('error', (err) => {
@@ -91,6 +91,7 @@ class Beech {
91
91
  const spawnData = new Promise((resolve) => {
92
92
  // check Dev. run service
93
93
  if(argument == "-D" || argument == "--dev") {
94
+ console.log("\n Starting Beech service in Development mode ");
94
95
  promise = this.spawn('npx', ['nodemon', '-q', './cli/beech']); // For Dev.
95
96
  } else {
96
97
  promise = this.spawn('npx', ['nodemon', '-q', './node_modules/beech-api/packages/cli/beech']); // For Prd.
@@ -37,74 +37,96 @@ const credentials = (req, res, next) => {
37
37
  if (passport_config.jwt_allow === true) {
38
38
  const auth_endpoint = (passport_config.auth_endpoint) ? (passport_config.auth_endpoint[ 0 ] === "/" ? passport_config.auth_endpoint : "/" + passport_config.auth_endpoint) : "/authentication";
39
39
  const slashOneIsHash = auth_endpoint.split("/")[1];
40
- // Check first HASH equal Authentication
41
- if(slashOneIsHash == req.params.hash) {
42
- // Bypass authentication for auth endpoint
43
- return next();
40
+ if(req.params === undefined) {
41
+ // Request is not valid
42
+ console.log("\n REQUEST|OPTION  Error: Request is not valid, missing params.");
43
+ return;
44
44
  } else {
45
- return passport.authenticate("jwt", {
46
- session: false,
47
- }, (err, user, info) => {
48
- // error check
49
- if (err) {
50
- console.log(err, info);
51
- return res.status(401).json({
52
- code: 401,
53
- error: "UNAUTHORIZED",
54
- message: {
55
- name: "WrongTokenError",
56
- message: "token error.",
57
- },
58
- /* dev: { err, info }, */ // for dev info
59
- });
60
- } else {
61
- // anything token check
62
- if (!user) {
63
- if (info) {
64
- if (info.name == "TokenExpiredError") {
65
- return res.status(401).json({
66
- code: 401,
67
- status: "TOKEN_EXPIRED",
68
- message: info,
69
- });
70
- }
71
- if (info.name == "Error") {
72
- return res.status(401).json({
73
- code: 401,
74
- status: "NO_AUTH_TOKEN",
75
- message: {
76
- name: "NoTokenError",
77
- message: "No auth token",
78
- },
79
- });
80
- }
81
- if (info.name == "SyntaxError") {
45
+ // Check first HASH equal Authentication
46
+ if(slashOneIsHash == req.params.hash) {
47
+ // Bypass authentication for auth endpoint
48
+ return next();
49
+ } else {
50
+ return passport.authenticate("jwt", {
51
+ session: false,
52
+ }, (err, user, info) => {
53
+ // error check
54
+ if (err) {
55
+ console.log(err, info);
56
+ return res.status(401).json({
57
+ code: 401,
58
+ error: "UNAUTHORIZED",
59
+ message: {
60
+ name: "WrongTokenError",
61
+ message: "token error.",
62
+ },
63
+ /* dev: { err, info }, */ // for dev info
64
+ });
65
+ } else {
66
+ // anything token check
67
+ if (!user) {
68
+ if (info) {
69
+ if (info.name == "TokenExpiredError") {
70
+ return res.status(401).json({
71
+ code: 401,
72
+ status: "TOKEN_EXPIRED",
73
+ message: info,
74
+ });
75
+ } else if (info.name == "Error") {
76
+ return res.status(401).json({
77
+ code: 401,
78
+ status: "NO_AUTH_TOKEN",
79
+ message: {
80
+ name: "NoTokenError",
81
+ message: "No auth token",
82
+ },
83
+ });
84
+ } else if (info.name == "SyntaxError") {
85
+ return res.status(401).json({
86
+ code: 401,
87
+ status: "PAYLOAD_SYNTAX_ERROR",
88
+ message: {
89
+ name: "SyntaxError",
90
+ message: "Unexpected token < in JSON at position 0",
91
+ },
92
+ });
93
+ } else if (info.name == "JsonWebTokenError") {
94
+ return res.status(401).json({
95
+ code: 401,
96
+ status: "INVALID_TOKEN",
97
+ message: {
98
+ name: "JsonWebTokenError",
99
+ message: "invalid token.",
100
+ },
101
+ });
102
+ } else {
103
+ return res.status(401).json({
104
+ code: 401,
105
+ status: "OTHER_TOKEN_ERR",
106
+ message: {
107
+ name: "OtherTokenError",
108
+ message: String(info),
109
+ },
110
+ });
111
+ }
112
+ } else {
82
113
  return res.status(401).json({
83
114
  code: 401,
84
- status: "PAYLOAD_SYNTAX_ERROR",
85
- message: {
86
- name: "SyntaxError",
87
- message: "Unexpected token < in JSON at position 0",
115
+ status: "UNAUTHORIZED_USER",
116
+ message: info || {
117
+ name: "TokenError",
118
+ message: String(info),
88
119
  },
89
120
  });
90
121
  }
91
122
  } else {
92
- return res.status(401).json({
93
- code: 401,
94
- status: "UNAUTHORIZED_USER",
95
- message: info || {
96
- name: "TokenError",
97
- message: "Unauthorized token."
98
- },
99
- });
123
+ // Perfectly user jwt
124
+ return next();
100
125
  }
101
- } else {
102
- // Perfectly user jwt
103
- return next();
104
126
  }
105
127
  }
106
- }
107
- )(req, res, next);
128
+ )(req, res, next);
129
+ }
108
130
  }
109
131
  } else {
110
132
  // Bypass authentication
@@ -18,7 +18,7 @@ function filterDbIsTrue(dbConfig, cb) {
18
18
  }
19
19
  setTimeout(() => {
20
20
  cb(null, dbIsTrue);
21
- }, 300);
21
+ }, 200);
22
22
  }
23
23
  });
24
24
  } catch (error) {
@@ -5,15 +5,14 @@
5
5
  "license": "MIT",
6
6
  "main": "index.js",
7
7
  "scripts": {
8
- "prod": "set NODE_ENV=production&& npm start",
9
- "production": "set NODE_ENV=production&& npm start",
10
- "dev": "set NODE_ENV=development&& npm start",
11
- "development": "set NODE_ENV=development&& npm start",
12
- "start": "beech-service serve",
8
+ "predev": "node ./node_modules/beech-api/packages/cli/core/test/check-node.js",
9
+ "prebuild": "node ./node_modules/beech-api/packages/cli/core/test/check-node.js",
10
+ "build": "set NODE_ENV=production&& beech-service build",
11
+ "dev": "beech-service serve",
13
12
  "test": "node ./node_modules/jest/bin/jest __tests__ -o --watch --config"
14
13
  },
15
14
  "dependencies": {
16
- "beech-api": "~3.9.0"
15
+ "beech-api": "^3.9.0"
17
16
  },
18
17
  "devDependencies": {
19
18
  "jest": "^29.7.0"
@@ -1,5 +1,5 @@
1
+ const CronJob = require("cron").CronJob;
1
2
  //const Users = require("@/models/Users");
2
- //const CronJob = require("cron").CronJob;
3
3
 
4
4
  exports.init = () => {
5
5
 
@@ -8,15 +8,25 @@ exports.init = () => {
8
8
  *
9
9
  */
10
10
 
11
- //Users.findAll().then(console.log)
11
+ // const getUsers = async () => {
12
+ // try {
13
+ // const users = await Users.findAll();
14
+ // console.log(users);
15
+ // } catch (error) {
16
+ // console.error(error);
17
+ // }
18
+ // };
12
19
 
13
20
  /**
14
- * Job schedule: seccod minute hour dayOfMonth month dayOfWeek
21
+ * Job schedule: seconds minutes hours dayOfMonth month dayOfWeek
15
22
  * Learn more: https://github.com/kelektiv/node-cron
16
23
  *
17
24
  */
18
- //new CronJob("0 0 0 * * *", () => {
19
- //Run job.
20
- //}, null, true, "Asia/Bangkok");
25
+ new CronJob("0 0 0 * * *", () => {
26
+ // Run job.
27
+
28
+ // getUsers();
29
+
30
+ }, null, true, "Asia/Bangkok");
21
31
 
22
32
  };
@@ -179,6 +179,18 @@ class Generator {
179
179
  resolve("\n Faltal  commnad it's not available.");
180
180
  }
181
181
  }
182
+ } else if (this.option == 'update') {
183
+ console.log('------------------------------');
184
+ if(this.argument == 'model') {
185
+ // this.special = model name
186
+ // if (!this.special) {
187
+ // resolve("\n [103m [90m Warning [0m [0m Please specify model name to update.");
188
+ // } else {
189
+ // this.updateModel()
190
+ // .then(res => resolve(res))
191
+ // .catch(err => reject(err));
192
+ // }
193
+ }
182
194
  } else if (this.option == 'passport') {
183
195
  if (this.argument == "init") {
184
196
  this.makePassportInit()
@@ -451,6 +463,74 @@ class Generator {
451
463
  });
452
464
  }
453
465
 
466
+ updateModel() {
467
+ return new Promise((resolve, reject) => {
468
+ try {
469
+ // 1. อ่านการตั้งค่าจาก global.config และ app.config เหมือนตอนสร้าง Model
470
+ this.fs.readFile("./global.config.js", 'utf8', (err, globalData) => {
471
+ if (err) return resolve("\n [101m Faltal [0m Can't read `global.config.js` file.");
472
+
473
+ this.fs.readFile("./app.config.js", 'utf8', (appErr, appData) => {
474
+ if (appErr) return resolve("\n [101m Faltal [0m Can't read `app.config.js` file.");
475
+
476
+ let appBuf2eval = eval(appData);
477
+ let modelName = this.special; // <table_name>
478
+ let modelPath = `./src/models/${modelName.charAt(0).toUpperCase() + modelName.slice(1)}.js`;
479
+
480
+ // ตรวจสอบว่ามีไฟล์ Model เดิมอยู่หรือไม่
481
+ if (!this.fs.existsSync(modelPath)) {
482
+ return resolve(`\n [103m [90m Warning [0m [0m Model file \`${modelName}\` not found to update.`);
483
+ }
484
+
485
+ // ให้ผู้ใช้เลือก Connection ที่ต้องการดึง Schema ใหม่
486
+ inquirer.prompt([{
487
+ type: "list",
488
+ name: "selectDbConnect",
489
+ message: " [93mSelect database connection to sync schema: [0m",
490
+ choices: appBuf2eval.database_config.map(e => e.name),
491
+ }]).then(dbSelected => {
492
+
493
+ // 2. ดึง Schema ใหม่จาก DB
494
+ const { connectForGenerateModel } = require("../databases/test");
495
+ let newModelClassName = modelName.split("_").map(e => e.charAt(0).toUpperCase() + e.slice(1)).join("");
496
+
497
+ connectForGenerateModel(dbSelected.selectDbConnect, modelName, appBuf2eval.database_config, (err, tableSchema, tableName) => {
498
+ if (err) return reject(err);
499
+
500
+ // 3. แปลง Schema เป็น Code String
501
+ this.rawSchemaTable(dbSelected.selectDbConnect, newModelClassName, tableName, tableSchema, (SchemaErr, rawSchema) => {
502
+ if (SchemaErr) return reject(SchemaErr);
503
+
504
+ // 4. อ่านไฟล์เดิมและทำการ Replace เฉพาะส่วน Schema
505
+ this.fs.readFile(modelPath, 'utf8', (readErr, currentContent) => {
506
+ if (readErr) return reject(readErr);
507
+
508
+ // ใช้ Regex เพื่อหาช่วงของ Schema(...).define(...) และเปลี่ยนเฉพาะข้างใน
509
+ // โดยจะหาตั้งแต่ Schema(sql.db).define("table", { จนถึง });
510
+ const regex = /const\s+\w+\s+=\s+Schema\(sql\.\w+\)\.define\([\s\S]*?\}\);/;
511
+
512
+ if (regex.test(currentContent)) {
513
+ let updatedContent = currentContent.replace(regex, rawSchema);
514
+
515
+ this.fs.writeFile(modelPath, updatedContent, 'utf8', (writeErr) => {
516
+ if (writeErr) return reject(writeErr);
517
+ resolve(`\n [102m [90m Passed [0m [0m The model \`${modelName}\` schema has been updated.`);
518
+ });
519
+ } else {
520
+ resolve("\n [101m Faltal [0m Could not find Schema definition pattern in the file.");
521
+ }
522
+ });
523
+ });
524
+ });
525
+ });
526
+ });
527
+ });
528
+ } catch (error) {
529
+ reject(error);
530
+ }
531
+ });
532
+ }
533
+
454
534
  generateModel(tmpModelsPath, dbSelected, appBuf2eval, pool_base) {
455
535
  return new Promise((resolve, reject) => {
456
536
  try {