lamix 4.2.26 → 4.2.27

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
@@ -173,6 +173,8 @@ const maxId = await User.query().max('id');
173
173
  # status a column (returns status of boolean values)
174
174
  const usersAll = await User.all();
175
175
  const users = await User.query().where('status', true).get();
176
+ # Randomize a column (returns Random values without any order)
177
+ const users = await User.query().where('status', true).orderByRaw('RAND()').get();
176
178
  const usersorderbyid = await User.query().where('status', true).orderBy('id', 'desc').get();
177
179
  const usersorderbycreateAt = await User.query().where('status', true).orderBy('created_at', 'desc').get();
178
180
 
@@ -333,6 +333,10 @@ export class QueryBuilder {
333
333
  having(column: any, operatorOrValue: any, value: any, ...args: any[]): this;
334
334
  orHaving(column: any, operatorOrValue: any, value: any, ...args: any[]): this;
335
335
  _pushHaving(column: any, op: any, value: any, bool: any): this;
336
+ /**************************************************************************
337
+ * ORDER BY RAW
338
+ **************************************************************************/
339
+ orderByRaw(rawSql: any): this;
336
340
  /**************************************************************************
337
341
  * ORDER / LIMIT
338
342
  **************************************************************************/
@@ -395,7 +399,7 @@ export class QueryBuilder {
395
399
  decrement(col: any, by?: number): Promise<any>;
396
400
  delete(): Promise<any>;
397
401
  truncate(): Promise<boolean>;
398
- _compileWhereOnly(): string;
402
+ _compileWhereOnly(allowEmpty?: boolean): string;
399
403
  /**************************************************************************
400
404
  * EAGER LOAD (unchanged except robust checks)
401
405
  **************************************************************************/
package/lib/index.js CHANGED
@@ -818,7 +818,7 @@ class Validator {
818
818
  // For strings or arrays, use length
819
819
  if (typeof val === 'string' || Array.isArray(val)) {
820
820
  if (val.length < limit) {
821
- this.addError(field, 'min', `${field} must be at least ${limit} characters.`);
821
+ this.addError(field, this.msg(field, 'min', `${field} must be at least ${limit} characters.`));
822
822
  }
823
823
  }
824
824
  // For numbers, compare numerically
@@ -853,8 +853,15 @@ class Validator {
853
853
  validateConfirmed(field) {
854
854
  const value = this.data[field];
855
855
  const confirm = this.data[field + '_confirmation'];
856
- if (value !== confirm) {
857
- this.addError(field, this.msg(field, 'confirmed', `${field} confirmation does not match.`));
856
+ if (!value) return;
857
+
858
+ const v1 = String(value).trim();
859
+ const v2 = String(confirm ?? '').trim();
860
+ if (v1 !== v2) {
861
+ this.addError(
862
+ field,
863
+ this.msg(field, 'confirmed', `${field} confirmation does not match.`)
864
+ );
858
865
  }
859
866
  }
860
867
 
@@ -1011,14 +1018,31 @@ class Validator {
1011
1018
  }
1012
1019
  }
1013
1020
 
1014
- validatePhone(field) {
1021
+ validatePhone(field) {
1015
1022
  const val = this.data[field];
1016
1023
  if (!val) return;
1017
- const s = String(val);
1018
- const phoneRegex = /^(0\d{9}|\+[1-9]\d{6,14})$/;
1019
- if (!phoneRegex.test(s)) {
1020
- this.addError(field, this.msg(field, 'phone', `${field} must be a valid phone number.`));
1024
+ let cleaned = String(val).replace(/[\s\-()]/g, '');
1025
+ /*
1026
+ Allow:
1027
+ - +countrycode + number (8–15 digits total)
1028
+ - numbers starting with 0
1029
+ - numbers starting with country code (no +)
1030
+ - 7–15 digits total
1031
+ */
1032
+ const phoneRegex = /^(\+?\d{8,15}|0\d{8,14})$/;
1033
+
1034
+ if (!phoneRegex.test(cleaned)) {
1035
+ this.addError(
1036
+ field,
1037
+ this.msg(field, 'phone', `${field} must be a valid phone number.`)
1038
+ );
1039
+ return;
1021
1040
  }
1041
+
1042
+ // if (!cleaned.startsWith('+')) {
1043
+ // cleaned = '+' + cleaned;
1044
+ // }
1045
+ // this.data[field] = cleaned;
1022
1046
  }
1023
1047
 
1024
1048
  validateAlpha(field) {
@@ -1481,7 +1505,7 @@ class Paginator {
1481
1505
  }
1482
1506
 
1483
1507
 
1484
- const VALID_OPERATORS = ['=', '<', '<=', '>', '>=', '<>', '!=', 'LIKE', 'ILIKE'];
1508
+ const VALID_OPERATORS = ['=', '<', '<=', '>', '>=', '<>', '!=', 'LIKE', 'ILIKE', 'NOT IN'];
1485
1509
  /******************************************************************************
1486
1510
  * QueryBuilder (Bug-Free)
1487
1511
  *****************************************************************************/
@@ -1513,7 +1537,7 @@ class QueryBuilder {
1513
1537
 
1514
1538
  // Helper to normalize operator
1515
1539
  _normalizeOperator(operator) {
1516
- const op = operator ? operator.toUpperCase() : '='
1540
+ let op = operator ? operator.toUpperCase() : '=';
1517
1541
  if (!VALID_OPERATORS.includes(op)) {
1518
1542
  throw new DBError('Invalid SQL operator', {
1519
1543
  operator,
@@ -1522,14 +1546,10 @@ class QueryBuilder {
1522
1546
  });
1523
1547
  }
1524
1548
 
1525
- // Convert ILIKE to proper operator for MySQL
1526
- if (op === 'ILIKE' && this.dialect === 'mysql') {
1527
- return 'LIKE';
1528
- }
1529
-
1549
+ // Convert ILIKE to LIKE for MySQL
1550
+ if (op === 'ILIKE' && this.dialect === 'mysql') op = 'LIKE';
1530
1551
  return op;
1531
1552
  }
1532
-
1533
1553
  /**************************************************************************
1534
1554
  * BASIC CONFIG
1535
1555
  **************************************************************************/
@@ -1619,7 +1639,7 @@ class QueryBuilder {
1619
1639
 
1620
1640
  const op = this._normalizeOperator(operator);
1621
1641
 
1622
- // For MySQL + LIKE case-insensitive, wrap with LOWER()
1642
+ // Use LOWER() only if original operator was ILIKE on MySQL
1623
1643
  const useLower = this.dialect === 'mysql' && operator.toUpperCase() === 'ILIKE';
1624
1644
  return this._pushWhere({
1625
1645
  type: 'basic',
@@ -1744,8 +1764,7 @@ class QueryBuilder {
1744
1764
  /** COMPLETELY FIXED VERSION */
1745
1765
  whereNot(column, operatorOrValue, value) {
1746
1766
  if (typeof column === 'object' && column !== null) {
1747
- for (const [k, v] of Object.entries(column))
1748
- this.whereNot(k, v);
1767
+ for (const [k, v] of Object.entries(column)) this.whereNot(k, v);
1749
1768
  return this;
1750
1769
  }
1751
1770
 
@@ -1760,11 +1779,13 @@ class QueryBuilder {
1760
1779
  if (operator === '=') operator = '!=';
1761
1780
  if (operator.toUpperCase() === 'IN') operator = 'NOT IN';
1762
1781
 
1782
+ const op = this._normalizeOperator(operator);
1783
+
1763
1784
  return this._pushWhere({
1764
1785
  type: 'basic',
1765
1786
  not: true,
1766
1787
  column,
1767
- operator,
1788
+ operator: op,
1768
1789
  value: val,
1769
1790
  bindings: [val]
1770
1791
  });
@@ -1906,6 +1927,22 @@ class QueryBuilder {
1906
1927
  return this;
1907
1928
  }
1908
1929
 
1930
+ /**************************************************************************
1931
+ * ORDER BY RAW
1932
+ **************************************************************************/
1933
+ orderByRaw(rawSql) {
1934
+ if (typeof rawSql !== 'string' || !rawSql.trim()) {
1935
+ throw new DBError('orderByRaw expects a non-empty string', {
1936
+ method: 'orderByRaw',
1937
+ rawSql
1938
+ });
1939
+ }
1940
+
1941
+ // Push as a special type
1942
+ this._orders.push({ raw: rawSql });
1943
+ return this;
1944
+ }
1945
+
1909
1946
  /**************************************************************************
1910
1947
  * ORDER / LIMIT
1911
1948
  **************************************************************************/
@@ -2092,7 +2129,7 @@ class QueryBuilder {
2092
2129
  parts.push(
2093
2130
  'ORDER BY ' +
2094
2131
  this._orders
2095
- .map(([c, d]) => `${escapeId(c)} ${d}`)
2132
+ .map(o => (Array.isArray(o) ? `${escapeId(o[0])} ${o[1]}` : o.raw))
2096
2133
  .join(', ')
2097
2134
  );
2098
2135
  }
@@ -2558,7 +2595,13 @@ class QueryBuilder {
2558
2595
  }
2559
2596
  }
2560
2597
 
2561
- _compileWhereOnly() {
2598
+ _compileWhereOnly(allowEmpty = false) {
2599
+ if (!allowEmpty && !this._wheres.length) {
2600
+ throw new DBError('Unsafe query without WHERE clause', {
2601
+ table: this.table,
2602
+ method: '_compileWhereOnly'
2603
+ });
2604
+ }
2562
2605
  const w = this._compileWheres();
2563
2606
  return w ? w : '';
2564
2607
  }
@@ -2597,10 +2640,9 @@ class QueryBuilder {
2597
2640
  **************************************************************************/
2598
2641
  _clone() {
2599
2642
  const c = new QueryBuilder(this.table, this.modelClass);
2600
-
2601
2643
  c.tableAlias = this.tableAlias;
2602
2644
  c._select = [...this._select];
2603
- c._joins = JSON.parse(JSON.stringify(this._joins));
2645
+ c._joins = this._joins.map(j => ({ ...j })); // shallow copy sufficient
2604
2646
  c._group = [...this._group];
2605
2647
  c._orders = [...this._orders];
2606
2648
  c._limit = this._limit;
@@ -2611,17 +2653,12 @@ class QueryBuilder {
2611
2653
  c._ignoreSoftDeletes = this._ignoreSoftDeletes;
2612
2654
  c._fromRaw = this._fromRaw;
2613
2655
 
2614
- // rehydrate nested queries
2656
+ // Rehydrate wheres, CTEs, unions
2615
2657
  c._wheres = this._rehydrateWheres(this._wheres);
2616
-
2617
- // rehydrate CTEs
2618
2658
  c._ctes = this._rehydrateCTEs(this._ctes);
2619
-
2620
- // rehydrate unions
2621
2659
  c._unions = this._rehydrateUnions(this._unions);
2622
2660
 
2623
- // having is simple
2624
- c._having = JSON.parse(JSON.stringify(this._having));
2661
+ c._having = this._having.map(h => ({ ...h }));
2625
2662
 
2626
2663
  return c;
2627
2664
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lamix",
3
- "version": "4.2.26",
3
+ "version": "4.2.27",
4
4
  "description": "lamix - ORM for Node-express js",
5
5
  "main": "./lib/index.js",
6
6
  "type": "commonjs",
@@ -26,14 +26,14 @@
26
26
  },
27
27
  "devDependencies": {
28
28
  "@types/node": "^20.11.30",
29
+ "chalk": "^4.1.2",
29
30
  "typescript": "^5.4.5"
30
31
  },
31
32
  "dependencies": {
32
33
  "bcrypt": "^6.0.0",
34
+ "dotenv": "^17.2.4 ",
33
35
  "express-session": "^1.19.0",
34
- "lru-cache": "^11.2.5",
35
- "chalk": "^4.1.2",
36
- "dotenv": "^17.2.4 "
36
+ "lru-cache": "^11.2.5"
37
37
  },
38
38
  "keywords": [
39
39
  "lamix",