spice-js 2.6.54 → 2.6.56

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.
@@ -8,6 +8,13 @@ function _asyncToGenerator(fn) { return function () { var self = this, args = ar
8
8
 
9
9
  var NodeCache = require("node-cache");
10
10
 
11
+ var crypto = require("crypto");
12
+
13
+ function generateCacheKey(input) {
14
+ // Create an MD5 hash of the input string and return it in hexadecimal format
15
+ return crypto.createHash("md5").update(input).digest("hex");
16
+ }
17
+
11
18
  module.exports = class SpiceCache {
12
19
  constructor(args) {
13
20
  if (args === void 0) {
@@ -45,9 +52,9 @@ module.exports = class SpiceCache {
45
52
  ttl,
46
53
  namespace = ""
47
54
  } = working_options;
48
- if (value === undefined) return console.log("Value is undefined");
55
+ var workingKey = namespace + key;
49
56
 
50
- _this2.client.set(namespace + key, value, ttl || 60);
57
+ _this2.client.set(generateCacheKey(workingKey), value, ttl == undefined ? 60 : ttl);
51
58
  })();
52
59
  }
53
60
 
@@ -60,7 +67,12 @@ module.exports = class SpiceCache {
60
67
  }
61
68
 
62
69
  try {
63
- return _this3.client.get(key);
70
+ var {
71
+ namespace = ""
72
+ } = options;
73
+ var workingKey = namespace + key;
74
+ var cacheKey = generateCacheKey(workingKey);
75
+ return _this3.client.get(cacheKey);
64
76
  } catch (e) {
65
77
  return null;
66
78
  }
@@ -71,7 +83,8 @@ module.exports = class SpiceCache {
71
83
  var _this4 = this;
72
84
 
73
85
  return _asyncToGenerator(function* () {
74
- return yield _this4.client.has(key);
86
+ var cacheKey = generateCacheKey(key);
87
+ return yield _this4.client.has(cacheKey);
75
88
  })();
76
89
  }
77
90
 
@@ -533,7 +533,7 @@ class SpiceModel {
533
533
  var _this3 = this;
534
534
 
535
535
  return _asyncToGenerator(function* () {
536
- var obj = _this3.getCacheProviderObject();
536
+ var obj = _this3.getCacheProviderObject(_this3.type);
537
537
 
538
538
  var monitor_record = yield obj.get("monitor::" + _this3.type);
539
539
 
@@ -542,6 +542,7 @@ class SpiceModel {
542
542
  }
543
543
 
544
544
  if (monitor_record.time > (response == null ? void 0 : response.time)) {
545
+ if (_this3.type == "resourcedetail") console.log("Will Force Refresh");
545
546
  return true;
546
547
  }
547
548
 
@@ -551,7 +552,7 @@ class SpiceModel {
551
552
 
552
553
  setMonitor() {
553
554
  var current_time = new Date().getTime();
554
- var obj = this.getCacheProviderObject();
555
+ var obj = this.getCacheProviderObject(this.type);
555
556
  var key = "monitor::" + this.type;
556
557
  var value = {
557
558
  time: current_time
@@ -584,7 +585,7 @@ class SpiceModel {
584
585
  var cached_results = yield _this4.getCacheProviderObject(_this4.type).get(key);
585
586
  results = cached_results == null ? void 0 : cached_results.value;
586
587
 
587
- if ((cached_results == null ? void 0 : cached_results.value) == undefined || (yield _this4.shouldForceRefresh(cached_results)) || _this4.type == "workflow") {
588
+ if ((cached_results == null ? void 0 : cached_results.value) === undefined || (yield _this4.shouldForceRefresh(cached_results)) || _this4.type == "workflow") {
588
589
  results = yield _this4.database.get(args.id);
589
590
  yield _this4.getCacheProviderObject(_this4.type).set(key, {
590
591
  value: results,
@@ -661,7 +662,7 @@ class SpiceModel {
661
662
  var cached_results = yield _this6.getCacheProviderObject(_this6.type).get(key);
662
663
  results = cached_results == null ? void 0 : cached_results.value;
663
664
 
664
- if ((cached_results == null ? void 0 : cached_results.value) == undefined || (yield _this6.shouldForceRefresh(cached_results))) {
665
+ if ((cached_results == null ? void 0 : cached_results.value) === undefined || (yield _this6.shouldForceRefresh(cached_results))) {
665
666
  results = yield _this6.database.multi_get(args.ids, true);
666
667
 
667
668
  _this6.getCacheProviderObject(_this6.type).set(key, {
@@ -914,11 +915,11 @@ class SpiceModel {
914
915
  if (column === "meta().id") return undefined;
915
916
 
916
917
  if (!column.startsWith("`") && !column.endsWith("`")) {
917
- column = "`" + column + "`";
918
+ column = "`" + column.trim() + "`";
918
919
  }
919
920
 
920
921
  if (column && !column.includes(".") && !column.startsWith(this.type)) {
921
- column = "`" + this.type + "`." + column;
922
+ column = "`" + this.type + "`." + column.trim();
922
923
  }
923
924
 
924
925
  return column;
@@ -930,10 +931,28 @@ class SpiceModel {
930
931
 
931
932
  filterResultsByColumns(data, columns) {
932
933
  if (columns && columns !== "") {
933
- columns = columns.replace(/`/g, "").replace(/meta\(\)\.id/g, "id");
934
- var columnList = columns.split(",").map(column => column.includes(".") ? _.last(column.split(".")) : column);
935
- columnList.push("_permissions", "_permissions_", "id");
936
- return data.map(entry => _.pick(entry, columnList));
934
+ // Remove backticks and replace meta().id with id
935
+ var cleanedColumns = columns.replace(/`/g, "").replace(/meta\(\)\.id/g, "id"); // Process each column by splitting on comma and trimming
936
+
937
+ var columnList = cleanedColumns.split(",").map(col => col.trim()).map(col => {
938
+ // Check for alias with AS (case-insensitive)
939
+ var aliasMatch = col.match(/\s+AS\s+([\w]+)/i);
940
+
941
+ if (aliasMatch) {
942
+ return aliasMatch[1].trim();
943
+ } // Otherwise, if a dot is present, take the part after the last dot
944
+
945
+
946
+ if (col.includes(".")) {
947
+ return col.split(".").pop().trim();
948
+ }
949
+
950
+ return col;
951
+ }); // Ensure that essential keys are always picked
952
+
953
+ var requiredKeys = ["_permissions", "_permissions_", "id"];
954
+ var finalKeys = [...new Set([...columnList, ...requiredKeys])];
955
+ return data.map(entry => _.pick(entry, finalKeys));
937
956
  }
938
957
 
939
958
  return data;
@@ -982,6 +1001,69 @@ class SpiceModel {
982
1001
  return str.replace(/[^a-zA-Z0-9]/g, "");
983
1002
  }
984
1003
 
1004
+ fixColumnName(columns) {
1005
+ // Guard clause: if columns is not provided or not a string, return it as-is.
1006
+ if (!columns || typeof columns !== "string") {
1007
+ return columns;
1008
+ } // Split the columns string on commas and trim each column expression.
1009
+
1010
+
1011
+ var columnList = columns.split(",").map(col => col.trim()); // Object to keep track of alias usage to avoid duplicates.
1012
+
1013
+ var aliasTracker = {};
1014
+ var fixedColumns = columnList.map(col => {
1015
+ // Check if an explicit alias is already provided.
1016
+ var aliasRegex = /\s+AS\s+`?([\w]+)`?$/i;
1017
+ var aliasMatch = col.match(aliasRegex);
1018
+ var explicitAlias = aliasMatch ? aliasMatch[1] : null; // Remove the alias clause for easier processing.
1019
+
1020
+ var colWithoutAlias = explicitAlias ? col.replace(aliasRegex, "").trim() : col; // Use regex to extract the table and column names.
1021
+ // This regex matches optional backticks around each identifier.
1022
+
1023
+ var tableName = this.type;
1024
+ var columnName = "";
1025
+ var columnRegex = /^`?([\w]+)`?\.`?([\w]+)`?$/;
1026
+ var match = colWithoutAlias.match(columnRegex);
1027
+
1028
+ if (match) {
1029
+ tableName = match[1];
1030
+ columnName = match[2];
1031
+ } else {
1032
+ // If no table prefix is found, use the column name only.
1033
+ columnName = colWithoutAlias.replace(/`/g, "");
1034
+ } // Generate the alias.
1035
+ // If the column comes from a table different from this.type, then the alias
1036
+ // becomes tableName_columnName. Otherwise, just use the column name.
1037
+
1038
+
1039
+ var newAlias = columnName;
1040
+
1041
+ if (tableName && tableName !== this.type) {
1042
+ newAlias = tableName + "_" + columnName; // If an explicit alias was provided, favor that.
1043
+
1044
+ if (explicitAlias) {
1045
+ newAlias = explicitAlias;
1046
+ } // Avoid duplicates by appending a count if needed.
1047
+
1048
+
1049
+ if (aliasTracker.hasOwnProperty(newAlias)) {
1050
+ aliasTracker[newAlias]++;
1051
+ newAlias = newAlias + "_" + aliasTracker[newAlias];
1052
+ } else {
1053
+ aliasTracker[newAlias] = 0;
1054
+ } // If there's no explicit alias, add an alias clause.
1055
+
1056
+
1057
+ if (!explicitAlias) {
1058
+ return colWithoutAlias + " AS `" + newAlias + "`";
1059
+ }
1060
+ }
1061
+
1062
+ return col;
1063
+ });
1064
+ return fixedColumns.join(", ");
1065
+ }
1066
+
985
1067
  list(args) {
986
1068
  var _this12 = this;
987
1069
 
@@ -996,6 +1078,7 @@ class SpiceModel {
996
1078
  args.columns = _this12.prepColumns(args.columns);
997
1079
  if (args.mapping_dept) _this12[_mapping_dept] = args.mapping_dept;
998
1080
  var nestings = [..._this12.extractNestings((_args6 = args) == null ? void 0 : _args6.query, _this12.type), ..._this12.extractNestings((_args7 = args) == null ? void 0 : _args7.columns, _this12.type), ..._this12.extractNestings((_args8 = args) == null ? void 0 : _args8.sort, _this12.type)];
1081
+ args.columns = _this12.fixColumnName(args.columns);
999
1082
 
1000
1083
  var mappedNestings = _.compact(_.uniq(nestings).map(nesting => {
1001
1084
  var _prop$map;
@@ -1041,12 +1124,16 @@ class SpiceModel {
1041
1124
  var cachedResults = yield _this12.getCacheProviderObject(_this12.type).get(cacheKey);
1042
1125
  results = cachedResults == null ? void 0 : cachedResults.value;
1043
1126
 
1044
- if (!results || (yield _this12.shouldForceRefresh(cachedResults))) {
1127
+ if ((cachedResults == null ? void 0 : cachedResults.value) === undefined) {
1045
1128
  results = yield _this12.fetchResults(args, query);
1046
- yield _this12.getCacheProviderObject(_this12.type).set(cacheKey, {
1047
- value: results,
1048
- time: new Date().getTime()
1049
- }, _this12.getCacheConfig(_this12.type));
1129
+ } else {
1130
+ if (!results || (yield _this12.shouldForceRefresh(cachedResults))) {
1131
+ results = yield _this12.fetchResults(args, query);
1132
+ yield _this12.getCacheProviderObject(_this12.type).set(cacheKey, {
1133
+ value: results,
1134
+ time: new Date().getTime()
1135
+ }, _this12.getCacheConfig(_this12.type));
1136
+ }
1050
1137
  }
1051
1138
  } else {
1052
1139
  results = yield _this12.fetchResults(args, query);
@@ -1364,70 +1451,78 @@ class SpiceModel {
1364
1451
  var _this18 = this;
1365
1452
 
1366
1453
  return _asyncToGenerator(function* () {
1367
- //console.log("CTX INside Model DO", this[_ctx]);
1454
+ if (path_to_be_removed === void 0) {
1455
+ path_to_be_removed = [];
1456
+ }
1457
+
1368
1458
  try {
1369
- // run serializers
1370
- if (!path_to_be_removed) {
1371
- path_to_be_removed = [];
1372
- }
1459
+ var _this18$_serializers, _this18$_serializers$;
1460
+
1461
+ // Early exit if serialization should not run.
1462
+ if (!_this18.shouldSerializerRun(args, type)) {
1463
+ return data;
1464
+ } // Add external modifiers only once.
1465
+
1466
+
1467
+ if (_this18.type && !_this18[_external_modifier_loaded]) {
1468
+ _this18.addExternalModifiers(_this18.type);
1469
+
1470
+ _this18[_external_modifier_loaded] = true;
1471
+ } // Cache the modifiers lookup for the specified type.
1373
1472
 
1374
- if (_this18.shouldSerializerRun(args, type)) {
1375
- if (_this18.type != "" && _this18.type != undefined && _this18[_external_modifier_loaded] != true) {
1376
- _this18.addExternalModifiers(_this18.type);
1377
1473
 
1378
- _this18[_external_modifier_loaded] = true;
1474
+ var modifiers = ((_this18$_serializers = _this18[_serializers]) == null ? void 0 : (_this18$_serializers$ = _this18$_serializers[type]) == null ? void 0 : _this18$_serializers$.modifiers) || [];
1475
+
1476
+ for (var modifier of modifiers) {
1477
+ try {
1478
+ data = yield modifier(data, old_data, _this18[_ctx], _this18.type);
1479
+ } catch (error) {
1480
+ console.error("Modifier error in do_serialize:", error.stack);
1379
1481
  }
1482
+ } // Ensure data is always an array for consistent processing.
1380
1483
 
1381
- for (var i of _this18[_serializers][type]["modifiers"]) {
1382
- try {
1383
- data = yield i(data, old_data, _this18[_ctx], _this18.type);
1384
- } catch (e) {
1385
- console.log(e.stack);
1386
- }
1387
- } // make data an array
1388
1484
 
1485
+ var originalIsArray = Array.isArray(data);
1389
1486
 
1390
- var original_is_array = _.isArray(data);
1487
+ if (!originalIsArray) {
1488
+ data = [data];
1489
+ } // Compute the defaults from properties using reduce.
1391
1490
 
1392
- if (!original_is_array) {
1393
- data = Array.of(data);
1394
- } // get defaults from peroperties
1395
1491
 
1492
+ var defaults = Object.keys(_this18.props).reduce((acc, key) => {
1493
+ var _this18$props$key, _this18$props$key$def;
1396
1494
 
1397
- var defaults = {};
1495
+ var def = (_this18$props$key = _this18.props[key]) == null ? void 0 : (_this18$props$key$def = _this18$props$key.defaults) == null ? void 0 : _this18$props$key$def[type];
1398
1496
 
1399
- for (var _i in _this18.props) {
1400
- if (_this18.props[_i].defaults != undefined && _this18.props[_i].defaults[type] != undefined && _this18.props[_i].defaults[type] != undefined) {
1401
- defaults[_i] = _.isFunction(_this18.props[_i].defaults[type]) ? _this18.props[_i].defaults[type]({
1402
- old_data: data,
1403
- new_data: old_data
1404
- }) : _this18.props[_i].defaults[type];
1405
- }
1406
- } // apply defaults
1497
+ if (def !== undefined) {
1498
+ acc[key] = _.isFunction(def) ? def({
1499
+ old_data: data,
1500
+ new_data: old_data
1501
+ }) : def;
1502
+ }
1407
1503
 
1504
+ return acc;
1505
+ }, {}); // Merge defaults into each object.
1408
1506
 
1409
- for (var _i2 of data) {
1410
- _.defaults(_i2, defaults);
1411
- } // apply cleaners
1507
+ data = data.map(item => _.defaults(item, defaults)); // If type is "read", clean the data by omitting certain props.
1412
1508
 
1509
+ if (type === "read") {
1510
+ // Collect hidden properties from schema.
1511
+ var hiddenProps = Object.keys(_this18.props).filter(key => {
1512
+ var _this18$props$key2;
1413
1513
 
1414
- if (type == "read") {
1415
- var props_to_clean = ["deleted", "type", ...path_to_be_removed];
1514
+ return (_this18$props$key2 = _this18.props[key]) == null ? void 0 : _this18$props$key2.hide;
1515
+ }); // Combine default props to remove.
1416
1516
 
1417
- for (var _i3 in _this18.props) {
1418
- if (_this18.props[_i3].hide) {
1419
- props_to_clean.push(_i3);
1420
- }
1421
- }
1517
+ var propsToClean = ["deleted", "type", ...path_to_be_removed, ...hiddenProps];
1518
+ data = data.map(item => _.omit(item, propsToClean));
1519
+ } // Return in the original format (array or single object).
1422
1520
 
1423
- data = _.map(data, obj => _.omit(obj, props_to_clean));
1424
- }
1425
1521
 
1426
- return original_is_array ? data : _.first(data);
1427
- }
1428
- } catch (e) {
1429
- console.log(e.stack);
1430
- throw e;
1522
+ return originalIsArray ? data : data[0];
1523
+ } catch (error) {
1524
+ console.error("Error in do_serialize:", error.stack);
1525
+ throw error;
1431
1526
  }
1432
1527
  })();
1433
1528
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spice-js",
3
- "version": "2.6.54",
3
+ "version": "2.6.56",
4
4
  "description": "spice",
5
5
  "main": "build/index.js",
6
6
  "repository": {
@@ -1,4 +1,11 @@
1
1
  const NodeCache = require("node-cache");
2
+ const crypto = require("crypto");
3
+
4
+ function generateCacheKey(input) {
5
+ // Create an MD5 hash of the input string and return it in hexadecimal format
6
+ return crypto.createHash("md5").update(input).digest("hex");
7
+ }
8
+
2
9
  module.exports = class SpiceCache {
3
10
  constructor(args = {}) {
4
11
  this.options = args;
@@ -12,19 +19,27 @@ module.exports = class SpiceCache {
12
19
  async set(key, value, options = {}) {
13
20
  let working_options = { ...this.options, ...options };
14
21
  const { ttl, namespace = "" } = working_options;
15
- if (value === undefined) return console.log("Value is undefined");
16
- this.client.set(namespace + key, value, ttl || 60);
22
+ const workingKey = namespace + key;
23
+ this.client.set(
24
+ generateCacheKey(workingKey),
25
+ value,
26
+ ttl == undefined ? 60 : ttl
27
+ );
17
28
  }
18
29
 
19
30
  async get(key, options = {}) {
20
31
  try {
21
- return this.client.get(key);
32
+ const { namespace = "" } = options;
33
+ const workingKey = namespace + key;
34
+ const cacheKey = generateCacheKey(workingKey);
35
+ return this.client.get(cacheKey);
22
36
  } catch (e) {
23
37
  return null;
24
38
  }
25
39
  }
26
40
 
27
41
  async exists(key) {
28
- return await this.client.has(key);
42
+ const cacheKey = generateCacheKey(key);
43
+ return await this.client.has(cacheKey);
29
44
  }
30
45
  };
@@ -165,15 +165,17 @@ export default class SpiceModel {
165
165
  }
166
166
  case Number:
167
167
  case "number": {
168
- this[i] = _.isNumber(args.args[i])
169
- ? args.args[i]
168
+ this[i] =
169
+ _.isNumber(args.args[i]) ?
170
+ args.args[i]
170
171
  : Number(args.args[i]);
171
172
  break;
172
173
  }
173
174
  case Boolean:
174
175
  case "boolean": {
175
- this[i] = _.isBoolean(args.args[i])
176
- ? args.args[i]
176
+ this[i] =
177
+ _.isBoolean(args.args[i]) ?
178
+ args.args[i]
177
179
  : args.args[i] == "true" ||
178
180
  args.args[i] == 1 ||
179
181
  args.args[i] == "True";
@@ -186,15 +188,17 @@ export default class SpiceModel {
186
188
  }
187
189
  case Array:
188
190
  case "array": {
189
- this[i] = _.isArray(args.args[i])
190
- ? args.args[i]
191
+ this[i] =
192
+ _.isArray(args.args[i]) ?
193
+ args.args[i]
191
194
  : JSON.parse(args.args[i]);
192
195
  break;
193
196
  }
194
197
  case Object:
195
198
  case "object": {
196
- this[i] = _.isObject(args.args[i])
197
- ? args.args[i]
199
+ this[i] =
200
+ _.isObject(args.args[i]) ?
201
+ args.args[i]
198
202
  : JSON.parse(args.args[i]);
199
203
  break;
200
204
  }
@@ -458,12 +462,13 @@ export default class SpiceModel {
458
462
  }
459
463
 
460
464
  async shouldForceRefresh(response) {
461
- let obj = this.getCacheProviderObject();
465
+ let obj = this.getCacheProviderObject(this.type);
462
466
  let monitor_record = await obj.get(`monitor::${this.type}`);
463
467
  if (monitor_record == undefined) {
464
468
  return false;
465
469
  }
466
470
  if (monitor_record.time > response?.time) {
471
+ if (this.type == "resourcedetail") console.log("Will Force Refresh");
467
472
  return true;
468
473
  }
469
474
  return false;
@@ -471,7 +476,7 @@ export default class SpiceModel {
471
476
 
472
477
  setMonitor() {
473
478
  let current_time = new Date().getTime();
474
- let obj = this.getCacheProviderObject();
479
+ let obj = this.getCacheProviderObject(this.type);
475
480
  let key = `monitor::${this.type}`;
476
481
  let value = { time: current_time };
477
482
  obj.set(key, value, { ttl: 0 });
@@ -497,7 +502,7 @@ export default class SpiceModel {
497
502
 
498
503
  results = cached_results?.value;
499
504
  if (
500
- cached_results?.value == undefined ||
505
+ cached_results?.value === undefined ||
501
506
  (await this.shouldForceRefresh(cached_results)) ||
502
507
  this.type == "workflow"
503
508
  ) {
@@ -573,7 +578,7 @@ export default class SpiceModel {
573
578
  );
574
579
  results = cached_results?.value;
575
580
  if (
576
- cached_results?.value == undefined ||
581
+ cached_results?.value === undefined ||
577
582
  (await this.shouldForceRefresh(cached_results))
578
583
  ) {
579
584
  results = await this.database.multi_get(args.ids, true);
@@ -815,14 +820,14 @@ export default class SpiceModel {
815
820
  columnList.map((column) => {
816
821
  if (column === "meta().id") return undefined;
817
822
  if (!column.startsWith("`") && !column.endsWith("`")) {
818
- column = `\`${column}\``;
823
+ column = `\`${column.trim()}\``;
819
824
  }
820
825
  if (
821
826
  column &&
822
827
  !column.includes(".") &&
823
828
  !column.startsWith(this.type)
824
829
  ) {
825
- column = `\`${this.type}\`.${column}`;
830
+ column = `\`${this.type}\`.${column.trim()}`;
826
831
  }
827
832
  return column;
828
833
  })
@@ -835,14 +840,33 @@ export default class SpiceModel {
835
840
 
836
841
  filterResultsByColumns(data, columns) {
837
842
  if (columns && columns !== "") {
838
- columns = columns.replace(/`/g, "").replace(/meta\(\)\.id/g, "id");
839
- let columnList = columns
843
+ // Remove backticks and replace meta().id with id
844
+ const cleanedColumns = columns
845
+ .replace(/`/g, "")
846
+ .replace(/meta\(\)\.id/g, "id");
847
+
848
+ // Process each column by splitting on comma and trimming
849
+ const columnList = cleanedColumns
840
850
  .split(",")
841
- .map((column) =>
842
- column.includes(".") ? _.last(column.split(".")) : column
843
- );
844
- columnList.push("_permissions", "_permissions_", "id");
845
- return data.map((entry) => _.pick(entry, columnList));
851
+ .map((col) => col.trim())
852
+ .map((col) => {
853
+ // Check for alias with AS (case-insensitive)
854
+ const aliasMatch = col.match(/\s+AS\s+([\w]+)/i);
855
+ if (aliasMatch) {
856
+ return aliasMatch[1].trim();
857
+ }
858
+ // Otherwise, if a dot is present, take the part after the last dot
859
+ if (col.includes(".")) {
860
+ return col.split(".").pop().trim();
861
+ }
862
+ return col;
863
+ });
864
+
865
+ // Ensure that essential keys are always picked
866
+ const requiredKeys = ["_permissions", "_permissions_", "id"];
867
+ const finalKeys = [...new Set([...columnList, ...requiredKeys])];
868
+
869
+ return data.map((entry) => _.pick(entry, finalKeys));
846
870
  }
847
871
  return data;
848
872
  }
@@ -899,9 +923,76 @@ export default class SpiceModel {
899
923
  return str.replace(/[^a-zA-Z0-9]/g, "");
900
924
  }
901
925
 
926
+ fixColumnName(columns) {
927
+ // Guard clause: if columns is not provided or not a string, return it as-is.
928
+ if (!columns || typeof columns !== "string") {
929
+ return columns;
930
+ }
931
+
932
+ // Split the columns string on commas and trim each column expression.
933
+ const columnList = columns.split(",").map((col) => col.trim());
934
+ // Object to keep track of alias usage to avoid duplicates.
935
+ const aliasTracker = {};
936
+
937
+ const fixedColumns = columnList.map((col) => {
938
+ // Check if an explicit alias is already provided.
939
+ const aliasRegex = /\s+AS\s+`?([\w]+)`?$/i;
940
+ const aliasMatch = col.match(aliasRegex);
941
+ let explicitAlias = aliasMatch ? aliasMatch[1] : null;
942
+
943
+ // Remove the alias clause for easier processing.
944
+ let colWithoutAlias =
945
+ explicitAlias ? col.replace(aliasRegex, "").trim() : col;
946
+
947
+ // Use regex to extract the table and column names.
948
+ // This regex matches optional backticks around each identifier.
949
+ let tableName = this.type;
950
+ let columnName = "";
951
+ const columnRegex = /^`?([\w]+)`?\.`?([\w]+)`?$/;
952
+ const match = colWithoutAlias.match(columnRegex);
953
+ if (match) {
954
+ tableName = match[1];
955
+ columnName = match[2];
956
+ } else {
957
+ // If no table prefix is found, use the column name only.
958
+ columnName = colWithoutAlias.replace(/`/g, "");
959
+ }
960
+
961
+ // Generate the alias.
962
+ // If the column comes from a table different from this.type, then the alias
963
+ // becomes tableName_columnName. Otherwise, just use the column name.
964
+ let newAlias = columnName;
965
+ if (tableName && tableName !== this.type) {
966
+ newAlias = `${tableName}_${columnName}`;
967
+
968
+ // If an explicit alias was provided, favor that.
969
+ if (explicitAlias) {
970
+ newAlias = explicitAlias;
971
+ }
972
+
973
+ // Avoid duplicates by appending a count if needed.
974
+ if (aliasTracker.hasOwnProperty(newAlias)) {
975
+ aliasTracker[newAlias]++;
976
+ newAlias = `${newAlias}_${aliasTracker[newAlias]}`;
977
+ } else {
978
+ aliasTracker[newAlias] = 0;
979
+ }
980
+
981
+ // If there's no explicit alias, add an alias clause.
982
+ if (!explicitAlias) {
983
+ return `${colWithoutAlias} AS \`${newAlias}\``;
984
+ }
985
+ }
986
+ return col;
987
+ });
988
+
989
+ return fixedColumns.join(", ");
990
+ }
991
+
902
992
  async list(args = {}) {
903
993
  try {
904
994
  args.columns = this.prepColumns(args.columns);
995
+
905
996
  if (args.mapping_dept) this[_mapping_dept] = args.mapping_dept;
906
997
 
907
998
  const nestings = [
@@ -910,6 +1001,8 @@ export default class SpiceModel {
910
1001
  ...this.extractNestings(args?.sort, this.type),
911
1002
  ];
912
1003
 
1004
+ args.columns = this.fixColumnName(args.columns);
1005
+
913
1006
  const mappedNestings = _.compact(
914
1007
  _.uniq(nestings).map((nesting) => {
915
1008
  const prop = this.props[nesting];
@@ -930,11 +1023,11 @@ export default class SpiceModel {
930
1023
  if (args.is_full_text === "true" || args.is_custom_query === "true") {
931
1024
  query = args.query;
932
1025
  } else {
933
- query = args.filters
934
- ? this.makeQueryFromFilter(args.filters)
935
- : args.query
936
- ? `${args.query} AND (\`${this.type}\`.deleted = false OR \`${this.type}\`.deleted IS MISSING)`
937
- : `(\`${this.type}\`.deleted = false OR \`${this.type}\`.deleted IS MISSING)`;
1026
+ query =
1027
+ args.filters ? this.makeQueryFromFilter(args.filters)
1028
+ : args.query ?
1029
+ `${args.query} AND (\`${this.type}\`.deleted = false OR \`${this.type}\`.deleted IS MISSING)`
1030
+ : `(\`${this.type}\`.deleted = false OR \`${this.type}\`.deleted IS MISSING)`;
938
1031
  }
939
1032
 
940
1033
  if (hasSQLInjection(query)) {
@@ -943,13 +1036,14 @@ export default class SpiceModel {
943
1036
 
944
1037
  args.limit = Number(args.limit) || undefined;
945
1038
  args.offset = Number(args.offset) || 0;
946
- args.sort = args.sort
947
- ? args.sort
1039
+ args.sort =
1040
+ args.sort ?
1041
+ args.sort
948
1042
  .split(",")
949
1043
  .map((item) =>
950
- item.includes(".")
951
- ? item
952
- : `\`${this.type}\`.${this.formatSortComponent(item)}`
1044
+ item.includes(".") ? item : (
1045
+ `\`${this.type}\`.${this.formatSortComponent(item)}`
1046
+ )
953
1047
  )
954
1048
  .join(",")
955
1049
  : `\`${this.type}\`.created_at DESC`;
@@ -969,13 +1063,17 @@ export default class SpiceModel {
969
1063
  );
970
1064
  results = cachedResults?.value;
971
1065
 
972
- if (!results || (await this.shouldForceRefresh(cachedResults))) {
1066
+ if (cachedResults?.value === undefined) {
973
1067
  results = await this.fetchResults(args, query);
974
- await this.getCacheProviderObject(this.type).set(
975
- cacheKey,
976
- { value: results, time: new Date().getTime() },
977
- this.getCacheConfig(this.type)
978
- );
1068
+ } else {
1069
+ if (!results || (await this.shouldForceRefresh(cachedResults))) {
1070
+ results = await this.fetchResults(args, query);
1071
+ await this.getCacheProviderObject(this.type).set(
1072
+ cacheKey,
1073
+ { value: results, time: new Date().getTime() },
1074
+ this.getCacheConfig(this.type)
1075
+ );
1076
+ }
979
1077
  }
980
1078
  } else {
981
1079
  results = await this.fetchResults(args, query);
@@ -1238,9 +1336,9 @@ export default class SpiceModel {
1238
1336
  execute: async (data) => {
1239
1337
  return await this.mapToObject(
1240
1338
  data,
1241
- _.isString(properties[i].map.reference)
1242
- ? spice.models[properties[i].map.reference]
1243
- : properties[i].map.reference,
1339
+ _.isString(properties[i].map.reference) ?
1340
+ spice.models[properties[i].map.reference]
1341
+ : properties[i].map.reference,
1244
1342
  i,
1245
1343
  properties[i].map.destination || i,
1246
1344
  properties[i]
@@ -1256,9 +1354,9 @@ export default class SpiceModel {
1256
1354
  execute: async (data) => {
1257
1355
  return await this.mapToObjectArray(
1258
1356
  data,
1259
- _.isString(properties[i].map.reference)
1260
- ? spice.models[properties[i].map.reference]
1261
- : properties[i].map.reference,
1357
+ _.isString(properties[i].map.reference) ?
1358
+ spice.models[properties[i].map.reference]
1359
+ : properties[i].map.reference,
1262
1360
  i,
1263
1361
  properties[i].map.destination || i,
1264
1362
  properties[i]
@@ -1278,76 +1376,71 @@ export default class SpiceModel {
1278
1376
  }
1279
1377
  }
1280
1378
 
1281
- async do_serialize(data, type, old_data, args, path_to_be_removed) {
1282
- //console.log("CTX INside Model DO", this[_ctx]);
1379
+ async do_serialize(data, type, old_data, args, path_to_be_removed = []) {
1283
1380
  try {
1284
- // run serializers
1381
+ // Early exit if serialization should not run.
1382
+ if (!this.shouldSerializerRun(args, type)) {
1383
+ return data;
1384
+ }
1285
1385
 
1286
- if (!path_to_be_removed) {
1287
- path_to_be_removed = [];
1386
+ // Add external modifiers only once.
1387
+ if (this.type && !this[_external_modifier_loaded]) {
1388
+ this.addExternalModifiers(this.type);
1389
+ this[_external_modifier_loaded] = true;
1288
1390
  }
1289
1391
 
1290
- if (this.shouldSerializerRun(args, type)) {
1291
- if (
1292
- this.type != "" &&
1293
- this.type != undefined &&
1294
- this[_external_modifier_loaded] != true
1295
- ) {
1296
- this.addExternalModifiers(this.type);
1297
- this[_external_modifier_loaded] = true;
1298
- }
1299
- for (let i of this[_serializers][type]["modifiers"]) {
1300
- try {
1301
- data = await i(data, old_data, this[_ctx], this.type);
1302
- } catch (e) {
1303
- console.log(e.stack);
1304
- }
1392
+ // Cache the modifiers lookup for the specified type.
1393
+ const modifiers = this[_serializers]?.[type]?.modifiers || [];
1394
+ for (const modifier of modifiers) {
1395
+ try {
1396
+ data = await modifier(data, old_data, this[_ctx], this.type);
1397
+ } catch (error) {
1398
+ console.error("Modifier error in do_serialize:", error.stack);
1305
1399
  }
1400
+ }
1306
1401
 
1307
- // make data an array
1308
- let original_is_array = _.isArray(data);
1309
-
1310
- if (!original_is_array) {
1311
- data = Array.of(data);
1312
- }
1402
+ // Ensure data is always an array for consistent processing.
1403
+ const originalIsArray = Array.isArray(data);
1404
+ if (!originalIsArray) {
1405
+ data = [data];
1406
+ }
1313
1407
 
1314
- // get defaults from peroperties
1315
- let defaults = {};
1316
- for (let i in this.props) {
1317
- if (
1318
- this.props[i].defaults != undefined &&
1319
- this.props[i].defaults[type] != undefined &&
1320
- this.props[i].defaults[type] != undefined
1321
- ) {
1322
- defaults[i] = _.isFunction(this.props[i].defaults[type])
1323
- ? this.props[i].defaults[type]({
1324
- old_data: data,
1325
- new_data: old_data,
1326
- })
1327
- : this.props[i].defaults[type];
1328
- }
1329
- }
1330
- // apply defaults
1331
- for (let i of data) {
1332
- _.defaults(i, defaults);
1408
+ // Compute the defaults from properties using reduce.
1409
+ const defaults = Object.keys(this.props).reduce((acc, key) => {
1410
+ const def = this.props[key]?.defaults?.[type];
1411
+ if (def !== undefined) {
1412
+ acc[key] =
1413
+ _.isFunction(def) ?
1414
+ def({ old_data: data, new_data: old_data })
1415
+ : def;
1333
1416
  }
1417
+ return acc;
1418
+ }, {});
1334
1419
 
1335
- // apply cleaners
1336
- if (type == "read") {
1337
- let props_to_clean = ["deleted", "type", ...path_to_be_removed];
1338
- for (let i in this.props) {
1339
- if (this.props[i].hide) {
1340
- props_to_clean.push(i);
1341
- }
1342
- }
1343
- data = _.map(data, (obj) => _.omit(obj, props_to_clean));
1344
- }
1420
+ // Merge defaults into each object.
1421
+ data = data.map((item) => _.defaults(item, defaults));
1345
1422
 
1346
- return original_is_array ? data : _.first(data);
1423
+ // If type is "read", clean the data by omitting certain props.
1424
+ if (type === "read") {
1425
+ // Collect hidden properties from schema.
1426
+ const hiddenProps = Object.keys(this.props).filter(
1427
+ (key) => this.props[key]?.hide
1428
+ );
1429
+ // Combine default props to remove.
1430
+ const propsToClean = [
1431
+ "deleted",
1432
+ "type",
1433
+ ...path_to_be_removed,
1434
+ ...hiddenProps,
1435
+ ];
1436
+ data = data.map((item) => _.omit(item, propsToClean));
1347
1437
  }
1348
- } catch (e) {
1349
- console.log(e.stack);
1350
- throw e;
1438
+
1439
+ // Return in the original format (array or single object).
1440
+ return originalIsArray ? data : data[0];
1441
+ } catch (error) {
1442
+ console.error("Error in do_serialize:", error.stack);
1443
+ throw error;
1351
1444
  }
1352
1445
  }
1353
1446
  }