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.
- package/build/cache/providers/SpiceCache.js +17 -4
- package/build/models/SpiceModel.js +156 -61
- package/package.json +1 -1
- package/src/cache/providers/SpiceCache.js +19 -4
- package/src/models/SpiceModel.js +195 -102
|
@@ -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
|
-
|
|
55
|
+
var workingKey = namespace + key;
|
|
49
56
|
|
|
50
|
-
_this2.client.set(
|
|
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
|
-
|
|
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
|
-
|
|
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)
|
|
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)
|
|
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
|
-
|
|
934
|
-
var
|
|
935
|
-
|
|
936
|
-
|
|
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 (
|
|
1127
|
+
if ((cachedResults == null ? void 0 : cachedResults.value) === undefined) {
|
|
1045
1128
|
results = yield _this12.fetchResults(args, query);
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
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
|
-
|
|
1454
|
+
if (path_to_be_removed === void 0) {
|
|
1455
|
+
path_to_be_removed = [];
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1368
1458
|
try {
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1415
|
-
|
|
1514
|
+
return (_this18$props$key2 = _this18.props[key]) == null ? void 0 : _this18$props$key2.hide;
|
|
1515
|
+
}); // Combine default props to remove.
|
|
1416
1516
|
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
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
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
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,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
|
-
|
|
16
|
-
this.client.set(
|
|
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
|
-
|
|
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
|
-
|
|
42
|
+
const cacheKey = generateCacheKey(key);
|
|
43
|
+
return await this.client.has(cacheKey);
|
|
29
44
|
}
|
|
30
45
|
};
|
package/src/models/SpiceModel.js
CHANGED
|
@@ -165,15 +165,17 @@ export default class SpiceModel {
|
|
|
165
165
|
}
|
|
166
166
|
case Number:
|
|
167
167
|
case "number": {
|
|
168
|
-
this[i] =
|
|
169
|
-
|
|
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] =
|
|
176
|
-
|
|
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] =
|
|
190
|
-
|
|
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] =
|
|
197
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
839
|
-
|
|
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((
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
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 =
|
|
934
|
-
? this.makeQueryFromFilter(args.filters)
|
|
935
|
-
: args.query
|
|
936
|
-
|
|
937
|
-
|
|
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 =
|
|
947
|
-
|
|
1039
|
+
args.sort =
|
|
1040
|
+
args.sort ?
|
|
1041
|
+
args.sort
|
|
948
1042
|
.split(",")
|
|
949
1043
|
.map((item) =>
|
|
950
|
-
item.includes(".")
|
|
951
|
-
|
|
952
|
-
|
|
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 (
|
|
1066
|
+
if (cachedResults?.value === undefined) {
|
|
973
1067
|
results = await this.fetchResults(args, query);
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
this.
|
|
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
|
-
|
|
1243
|
-
|
|
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
|
-
|
|
1261
|
-
|
|
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
|
|
1381
|
+
// Early exit if serialization should not run.
|
|
1382
|
+
if (!this.shouldSerializerRun(args, type)) {
|
|
1383
|
+
return data;
|
|
1384
|
+
}
|
|
1285
1385
|
|
|
1286
|
-
|
|
1287
|
-
|
|
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
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
this[
|
|
1295
|
-
) {
|
|
1296
|
-
|
|
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
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
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
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
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
|
-
|
|
1336
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
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
|
}
|