spice-js 2.6.53 → 2.6.55

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.
@@ -19,8 +19,6 @@ function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try
19
19
 
20
20
  function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; }
21
21
 
22
- global.mapping_dept = "*";
23
-
24
22
  var co = require("co");
25
23
 
26
24
  var regeneratorRuntime = require("regenerator-runtime");
@@ -37,7 +35,8 @@ var SDate = require("sonover-date"),
37
35
  _external_modifier_loaded = Symbol(),
38
36
  _skip_cache = Symbol(),
39
37
  _serializers = Symbol(),
40
- _level = Symbol(); //const _type = Symbol("type");
38
+ _level = Symbol(),
39
+ _mapping_dept = Symbol(); //const _type = Symbol("type");
41
40
 
42
41
 
43
42
  var that;
@@ -59,20 +58,21 @@ class SpiceModel {
59
58
  }
60
59
 
61
60
  try {
62
- var _args$args, _args2, _args2$args, _args3, _args3$args, _args4, _args4$args;
61
+ var _args2, _args2$args, _args$args, _args3, _args3$args, _args4, _args4$args, _args5, _args5$args;
63
62
 
64
63
  var dbtype = spice.config.database.connections[args.connection].type || "couchbase";
65
64
 
66
65
  var Database = require("spice-" + dbtype);
67
66
 
67
+ this[_mapping_dept] = ((_args2 = args) == null ? void 0 : (_args2$args = _args2.args) == null ? void 0 : _args2$args.mapping_dept) || 100;
68
68
  this.type = "";
69
69
  this.collection = args.connection;
70
70
  this[_args] = args.args;
71
71
  this[_external_modifier_loaded] = false;
72
72
  this[_disable_lifecycle_events] = ((_args$args = args.args) == null ? void 0 : _args$args.disable_lifecycle_events) || false;
73
- this[_ctx] = (_args2 = args) == null ? void 0 : (_args2$args = _args2.args) == null ? void 0 : _args2$args.ctx;
74
- this[_skip_cache] = ((_args3 = args) == null ? void 0 : (_args3$args = _args3.args) == null ? void 0 : _args3$args.skip_cache) || false;
75
- this[_level] = ((_args4 = args) == null ? void 0 : (_args4$args = _args4.args) == null ? void 0 : _args4$args._level) || 0;
73
+ this[_ctx] = (_args3 = args) == null ? void 0 : (_args3$args = _args3.args) == null ? void 0 : _args3$args.ctx;
74
+ this[_skip_cache] = ((_args4 = args) == null ? void 0 : (_args4$args = _args4.args) == null ? void 0 : _args4$args.skip_cache) || false;
75
+ this[_level] = ((_args5 = args) == null ? void 0 : (_args5$args = _args5.args) == null ? void 0 : _args5$args._level) || 0;
76
76
  this[_hooks] = {
77
77
  create: {
78
78
  before: [],
@@ -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
 
@@ -551,7 +551,7 @@ class SpiceModel {
551
551
 
552
552
  setMonitor() {
553
553
  var current_time = new Date().getTime();
554
- var obj = this.getCacheProviderObject();
554
+ var obj = this.getCacheProviderObject(this.type);
555
555
  var key = "monitor::" + this.type;
556
556
  var value = {
557
557
  time: current_time
@@ -566,9 +566,7 @@ class SpiceModel {
566
566
 
567
567
  return _asyncToGenerator(function* () {
568
568
  try {
569
- var _args5;
570
-
571
- global.mapping_dept = ((_args5 = args) == null ? void 0 : _args5.mapping_dept) ? Number(args.mapping_dept) : global.mapping_dept;
569
+ if (args.mapping_dept) _this4[_mapping_dept] = args.mapping_dept;
572
570
 
573
571
  if (!args) {
574
572
  args = {};
@@ -586,7 +584,7 @@ class SpiceModel {
586
584
  var cached_results = yield _this4.getCacheProviderObject(_this4.type).get(key);
587
585
  results = cached_results == null ? void 0 : cached_results.value;
588
586
 
589
- if ((cached_results == null ? void 0 : cached_results.value) == undefined || (yield _this4.shouldForceRefresh(cached_results)) || _this4.type == "workflow") {
587
+ if ((cached_results == null ? void 0 : cached_results.value) === undefined || (yield _this4.shouldForceRefresh(cached_results)) || _this4.type == "workflow") {
590
588
  results = yield _this4.database.get(args.id);
591
589
  yield _this4.getCacheProviderObject(_this4.type).set(key, {
592
590
  value: results,
@@ -663,7 +661,7 @@ class SpiceModel {
663
661
  var cached_results = yield _this6.getCacheProviderObject(_this6.type).get(key);
664
662
  results = cached_results == null ? void 0 : cached_results.value;
665
663
 
666
- if ((cached_results == null ? void 0 : cached_results.value) == undefined || (yield _this6.shouldForceRefresh(cached_results))) {
664
+ if ((cached_results == null ? void 0 : cached_results.value) === undefined || (yield _this6.shouldForceRefresh(cached_results))) {
667
665
  results = yield _this6.database.multi_get(args.ids, true);
668
666
 
669
667
  _this6.getCacheProviderObject(_this6.type).set(key, {
@@ -916,11 +914,11 @@ class SpiceModel {
916
914
  if (column === "meta().id") return undefined;
917
915
 
918
916
  if (!column.startsWith("`") && !column.endsWith("`")) {
919
- column = "`" + column + "`";
917
+ column = "`" + column.trim() + "`";
920
918
  }
921
919
 
922
920
  if (column && !column.includes(".") && !column.startsWith(this.type)) {
923
- column = "`" + this.type + "`." + column;
921
+ column = "`" + this.type + "`." + column.trim();
924
922
  }
925
923
 
926
924
  return column;
@@ -931,11 +929,33 @@ class SpiceModel {
931
929
  }
932
930
 
933
931
  filterResultsByColumns(data, columns) {
932
+ if (this.type === "user") {
933
+ console.log("Filter Columns::", columns);
934
+ }
935
+
934
936
  if (columns && columns !== "") {
935
- columns = columns.replace(/`/g, "").replace(/meta\(\)\.id/g, "id");
936
- var columnList = columns.split(",").map(column => column.includes(".") ? _.last(column.split(".")) : column);
937
- columnList.push("_permissions", "_permissions_", "id");
938
- return data.map(entry => _.pick(entry, columnList));
937
+ // Remove backticks and replace meta().id with id
938
+ var cleanedColumns = columns.replace(/`/g, "").replace(/meta\(\)\.id/g, "id"); // Process each column by splitting on comma and trimming
939
+
940
+ var columnList = cleanedColumns.split(",").map(col => col.trim()).map(col => {
941
+ // Check for alias with AS (case-insensitive)
942
+ var aliasMatch = col.match(/\s+AS\s+([\w]+)/i);
943
+
944
+ if (aliasMatch) {
945
+ return aliasMatch[1].trim();
946
+ } // Otherwise, if a dot is present, take the part after the last dot
947
+
948
+
949
+ if (col.includes(".")) {
950
+ return col.split(".").pop().trim();
951
+ }
952
+
953
+ return col;
954
+ }); // Ensure that essential keys are always picked
955
+
956
+ var requiredKeys = ["_permissions", "_permissions_", "id"];
957
+ var finalKeys = [...new Set([...columnList, ...requiredKeys])];
958
+ return data.map(entry => _.pick(entry, finalKeys));
939
959
  }
940
960
 
941
961
  return data;
@@ -984,6 +1004,69 @@ class SpiceModel {
984
1004
  return str.replace(/[^a-zA-Z0-9]/g, "");
985
1005
  }
986
1006
 
1007
+ fixColumnName(columns) {
1008
+ // Guard clause: if columns is not provided or not a string, return it as-is.
1009
+ if (!columns || typeof columns !== "string") {
1010
+ return columns;
1011
+ } // Split the columns string on commas and trim each column expression.
1012
+
1013
+
1014
+ var columnList = columns.split(",").map(col => col.trim()); // Object to keep track of alias usage to avoid duplicates.
1015
+
1016
+ var aliasTracker = {};
1017
+ var fixedColumns = columnList.map(col => {
1018
+ // Check if an explicit alias is already provided.
1019
+ var aliasRegex = /\s+AS\s+`?([\w]+)`?$/i;
1020
+ var aliasMatch = col.match(aliasRegex);
1021
+ var explicitAlias = aliasMatch ? aliasMatch[1] : null; // Remove the alias clause for easier processing.
1022
+
1023
+ var colWithoutAlias = explicitAlias ? col.replace(aliasRegex, "").trim() : col; // Use regex to extract the table and column names.
1024
+ // This regex matches optional backticks around each identifier.
1025
+
1026
+ var tableName = this.type;
1027
+ var columnName = "";
1028
+ var columnRegex = /^`?([\w]+)`?\.`?([\w]+)`?$/;
1029
+ var match = colWithoutAlias.match(columnRegex);
1030
+
1031
+ if (match) {
1032
+ tableName = match[1];
1033
+ columnName = match[2];
1034
+ } else {
1035
+ // If no table prefix is found, use the column name only.
1036
+ columnName = colWithoutAlias.replace(/`/g, "");
1037
+ } // Generate the alias.
1038
+ // If the column comes from a table different from this.type, then the alias
1039
+ // becomes tableName_columnName. Otherwise, just use the column name.
1040
+
1041
+
1042
+ var newAlias = columnName;
1043
+
1044
+ if (tableName && tableName !== this.type) {
1045
+ newAlias = tableName + "_" + columnName; // If an explicit alias was provided, favor that.
1046
+
1047
+ if (explicitAlias) {
1048
+ newAlias = explicitAlias;
1049
+ } // Avoid duplicates by appending a count if needed.
1050
+
1051
+
1052
+ if (aliasTracker.hasOwnProperty(newAlias)) {
1053
+ aliasTracker[newAlias]++;
1054
+ newAlias = newAlias + "_" + aliasTracker[newAlias];
1055
+ } else {
1056
+ aliasTracker[newAlias] = 0;
1057
+ } // If there's no explicit alias, add an alias clause.
1058
+
1059
+
1060
+ if (!explicitAlias) {
1061
+ return colWithoutAlias + " AS `" + newAlias + "`";
1062
+ }
1063
+ }
1064
+
1065
+ return col;
1066
+ });
1067
+ return fixedColumns.join(", ");
1068
+ }
1069
+
987
1070
  list(args) {
988
1071
  var _this12 = this;
989
1072
 
@@ -993,10 +1076,12 @@ class SpiceModel {
993
1076
  }
994
1077
 
995
1078
  try {
996
- var _args6, _args7, _args8, _args9;
1079
+ var _args6, _args7, _args8;
997
1080
 
998
1081
  args.columns = _this12.prepColumns(args.columns);
1082
+ if (args.mapping_dept) _this12[_mapping_dept] = args.mapping_dept;
999
1083
  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)];
1084
+ args.columns = _this12.fixColumnName(args.columns);
1000
1085
 
1001
1086
  var mappedNestings = _.compact(_.uniq(nestings).map(nesting => {
1002
1087
  var _prop$map;
@@ -1028,7 +1113,6 @@ class SpiceModel {
1028
1113
 
1029
1114
  args.limit = Number(args.limit) || undefined;
1030
1115
  args.offset = Number(args.offset) || 0;
1031
- global.mapping_dept = ((_args9 = args) == null ? void 0 : _args9.mapping_dept) ? Number(args.mapping_dept) : global.mapping_dept;
1032
1116
  args.sort = args.sort ? args.sort.split(",").map(item => item.includes(".") ? item : "`" + _this12.type + "`." + _this12.formatSortComponent(item)).join(",") : "`" + _this12.type + "`.created_at DESC";
1033
1117
 
1034
1118
  if (args.skip_hooks !== true) {
@@ -1042,8 +1126,7 @@ class SpiceModel {
1042
1126
  if (_this12.shouldUseCache(_this12.type)) {
1043
1127
  var cachedResults = yield _this12.getCacheProviderObject(_this12.type).get(cacheKey);
1044
1128
  results = cachedResults == null ? void 0 : cachedResults.value;
1045
-
1046
- if (!results || (yield _this12.shouldForceRefresh(cachedResults))) {
1129
+ if ((cachedResults == null ? void 0 : cachedResults.value) === undefined) if (!results || (yield _this12.shouldForceRefresh(cachedResults))) {
1047
1130
  results = yield _this12.fetchResults(args, query);
1048
1131
  yield _this12.getCacheProviderObject(_this12.type).set(cacheKey, {
1049
1132
  value: results,
@@ -1058,6 +1141,8 @@ class SpiceModel {
1058
1141
  results.data = yield _this12.do_serialize(results.data, "read", {}, args, (yield _this12.propsToBeRemoved(results.data)));
1059
1142
  }
1060
1143
 
1144
+ if (_this12.type == "user") console.log("results", results.data);
1145
+
1061
1146
  if (args.skip_hooks !== true) {
1062
1147
  yield _this12.run_hook(results.data, "list", "after");
1063
1148
  }
@@ -1158,7 +1243,9 @@ class SpiceModel {
1158
1243
  data = Array.of(data);
1159
1244
  }
1160
1245
 
1161
- if (_this15[_level] < global.mapping_dept || global.mapping_dept == "*") {
1246
+ _this15[_mapping_dept];
1247
+
1248
+ if (_this15[_level] + 1 < _this15[_mapping_dept]) {
1162
1249
  var classes = _.isArray(Class) ? Class : [Class];
1163
1250
  var ids = [];
1164
1251
 
@@ -1171,7 +1258,8 @@ class SpiceModel {
1171
1258
  var returned_all = yield Promise.allSettled(_.map(classes, obj => {
1172
1259
  return new obj(_extends({}, _this15[_args], {
1173
1260
  skip_cache: _this15[_skip_cache],
1174
- _level: _this15[_level] + 1
1261
+ _level: _this15[_level] + 1,
1262
+ mapping_dept: _this15[_mapping_dept]
1175
1263
  })).getMulti({
1176
1264
  skip_hooks: true,
1177
1265
  ids: ids
@@ -1205,7 +1293,7 @@ class SpiceModel {
1205
1293
  data = Array.of(data);
1206
1294
  }
1207
1295
 
1208
- if (_this16[_level] < global.mapping_dept || global.mapping_dept == "*") {
1296
+ if (_this16[_level] + 1 < _this16[_mapping_dept]) {
1209
1297
  var ids = [];
1210
1298
 
1211
1299
  _.each(data, result => {
@@ -1228,7 +1316,8 @@ class SpiceModel {
1228
1316
  var returned_all = yield Promise.allSettled(_.map(classes, obj => {
1229
1317
  return new obj(_extends({}, _this16[_args], {
1230
1318
  skip_cache: _this16[_skip_cache],
1231
- _level: _this16[_level] + 1
1319
+ _level: _this16[_level] + 1,
1320
+ mapping_dept: _this16[_mapping_dept]
1232
1321
  })).getMulti({
1233
1322
  skip_hooks: true,
1234
1323
  ids: ids
@@ -1362,70 +1451,78 @@ class SpiceModel {
1362
1451
  var _this18 = this;
1363
1452
 
1364
1453
  return _asyncToGenerator(function* () {
1365
- //console.log("CTX INside Model DO", this[_ctx]);
1454
+ if (path_to_be_removed === void 0) {
1455
+ path_to_be_removed = [];
1456
+ }
1457
+
1366
1458
  try {
1367
- // run serializers
1368
- if (!path_to_be_removed) {
1369
- path_to_be_removed = [];
1370
- }
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);
1371
1469
 
1372
- if (_this18.shouldSerializerRun(args, type)) {
1373
- if (_this18.type != "" && _this18.type != undefined && _this18[_external_modifier_loaded] != true) {
1374
- _this18.addExternalModifiers(_this18.type);
1470
+ _this18[_external_modifier_loaded] = true;
1471
+ } // Cache the modifiers lookup for the specified type.
1375
1472
 
1376
- _this18[_external_modifier_loaded] = true;
1473
+
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);
1377
1481
  }
1482
+ } // Ensure data is always an array for consistent processing.
1378
1483
 
1379
- for (var i of _this18[_serializers][type]["modifiers"]) {
1380
- try {
1381
- data = yield i(data, old_data, _this18[_ctx], _this18.type);
1382
- } catch (e) {
1383
- console.log(e.stack);
1384
- }
1385
- } // make data an array
1386
1484
 
1485
+ var originalIsArray = Array.isArray(data);
1387
1486
 
1388
- var original_is_array = _.isArray(data);
1487
+ if (!originalIsArray) {
1488
+ data = [data];
1489
+ } // Compute the defaults from properties using reduce.
1389
1490
 
1390
- if (!original_is_array) {
1391
- data = Array.of(data);
1392
- } // get defaults from peroperties
1393
1491
 
1492
+ var defaults = Object.keys(_this18.props).reduce((acc, key) => {
1493
+ var _this18$props$key, _this18$props$key$def;
1394
1494
 
1395
- 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];
1396
1496
 
1397
- for (var _i in _this18.props) {
1398
- if (_this18.props[_i].defaults != undefined && _this18.props[_i].defaults[type] != undefined && _this18.props[_i].defaults[type] != undefined) {
1399
- defaults[_i] = _.isFunction(_this18.props[_i].defaults[type]) ? _this18.props[_i].defaults[type]({
1400
- old_data: data,
1401
- new_data: old_data
1402
- }) : _this18.props[_i].defaults[type];
1403
- }
1404
- } // apply defaults
1497
+ if (def !== undefined) {
1498
+ acc[key] = _.isFunction(def) ? def({
1499
+ old_data: data,
1500
+ new_data: old_data
1501
+ }) : def;
1502
+ }
1405
1503
 
1504
+ return acc;
1505
+ }, {}); // Merge defaults into each object.
1406
1506
 
1407
- for (var _i2 of data) {
1408
- _.defaults(_i2, defaults);
1409
- } // apply cleaners
1507
+ data = data.map(item => _.defaults(item, defaults)); // If type is "read", clean the data by omitting certain props.
1410
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;
1411
1513
 
1412
- if (type == "read") {
1413
- 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.
1414
1516
 
1415
- for (var _i3 in _this18.props) {
1416
- if (_this18.props[_i3].hide) {
1417
- props_to_clean.push(_i3);
1418
- }
1419
- }
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).
1420
1520
 
1421
- data = _.map(data, obj => _.omit(obj, props_to_clean));
1422
- }
1423
1521
 
1424
- return original_is_array ? data : _.first(data);
1425
- }
1426
- } catch (e) {
1427
- console.log(e.stack);
1428
- throw e;
1522
+ return originalIsArray ? data : data[0];
1523
+ } catch (error) {
1524
+ console.error("Error in do_serialize:", error.stack);
1525
+ throw error;
1429
1526
  }
1430
1527
  })();
1431
1528
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spice-js",
3
- "version": "2.6.53",
3
+ "version": "2.6.55",
4
4
  "description": "spice",
5
5
  "main": "build/index.js",
6
6
  "repository": {
@@ -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,7 +462,7 @@ 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;
@@ -471,7 +475,7 @@ export default class SpiceModel {
471
475
 
472
476
  setMonitor() {
473
477
  let current_time = new Date().getTime();
474
- let obj = this.getCacheProviderObject();
478
+ let obj = this.getCacheProviderObject(this.type);
475
479
  let key = `monitor::${this.type}`;
476
480
  let value = { time: current_time };
477
481
  obj.set(key, value, { ttl: 0 });
@@ -497,7 +501,7 @@ export default class SpiceModel {
497
501
 
498
502
  results = cached_results?.value;
499
503
  if (
500
- cached_results?.value == undefined ||
504
+ cached_results?.value === undefined ||
501
505
  (await this.shouldForceRefresh(cached_results)) ||
502
506
  this.type == "workflow"
503
507
  ) {
@@ -573,7 +577,7 @@ export default class SpiceModel {
573
577
  );
574
578
  results = cached_results?.value;
575
579
  if (
576
- cached_results?.value == undefined ||
580
+ cached_results?.value === undefined ||
577
581
  (await this.shouldForceRefresh(cached_results))
578
582
  ) {
579
583
  results = await this.database.multi_get(args.ids, true);
@@ -815,14 +819,14 @@ export default class SpiceModel {
815
819
  columnList.map((column) => {
816
820
  if (column === "meta().id") return undefined;
817
821
  if (!column.startsWith("`") && !column.endsWith("`")) {
818
- column = `\`${column}\``;
822
+ column = `\`${column.trim()}\``;
819
823
  }
820
824
  if (
821
825
  column &&
822
826
  !column.includes(".") &&
823
827
  !column.startsWith(this.type)
824
828
  ) {
825
- column = `\`${this.type}\`.${column}`;
829
+ column = `\`${this.type}\`.${column.trim()}`;
826
830
  }
827
831
  return column;
828
832
  })
@@ -834,15 +838,37 @@ export default class SpiceModel {
834
838
  }
835
839
 
836
840
  filterResultsByColumns(data, columns) {
841
+ if (this.type === "user") {
842
+ console.log("Filter Columns::", columns);
843
+ }
837
844
  if (columns && columns !== "") {
838
- columns = columns.replace(/`/g, "").replace(/meta\(\)\.id/g, "id");
839
- let columnList = columns
845
+ // Remove backticks and replace meta().id with id
846
+ const cleanedColumns = columns
847
+ .replace(/`/g, "")
848
+ .replace(/meta\(\)\.id/g, "id");
849
+
850
+ // Process each column by splitting on comma and trimming
851
+ const columnList = cleanedColumns
840
852
  .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));
853
+ .map((col) => col.trim())
854
+ .map((col) => {
855
+ // Check for alias with AS (case-insensitive)
856
+ const aliasMatch = col.match(/\s+AS\s+([\w]+)/i);
857
+ if (aliasMatch) {
858
+ return aliasMatch[1].trim();
859
+ }
860
+ // Otherwise, if a dot is present, take the part after the last dot
861
+ if (col.includes(".")) {
862
+ return col.split(".").pop().trim();
863
+ }
864
+ return col;
865
+ });
866
+
867
+ // Ensure that essential keys are always picked
868
+ const requiredKeys = ["_permissions", "_permissions_", "id"];
869
+ const finalKeys = [...new Set([...columnList, ...requiredKeys])];
870
+
871
+ return data.map((entry) => _.pick(entry, finalKeys));
846
872
  }
847
873
  return data;
848
874
  }
@@ -899,9 +925,76 @@ export default class SpiceModel {
899
925
  return str.replace(/[^a-zA-Z0-9]/g, "");
900
926
  }
901
927
 
928
+ fixColumnName(columns) {
929
+ // Guard clause: if columns is not provided or not a string, return it as-is.
930
+ if (!columns || typeof columns !== "string") {
931
+ return columns;
932
+ }
933
+
934
+ // Split the columns string on commas and trim each column expression.
935
+ const columnList = columns.split(",").map((col) => col.trim());
936
+ // Object to keep track of alias usage to avoid duplicates.
937
+ const aliasTracker = {};
938
+
939
+ const fixedColumns = columnList.map((col) => {
940
+ // Check if an explicit alias is already provided.
941
+ const aliasRegex = /\s+AS\s+`?([\w]+)`?$/i;
942
+ const aliasMatch = col.match(aliasRegex);
943
+ let explicitAlias = aliasMatch ? aliasMatch[1] : null;
944
+
945
+ // Remove the alias clause for easier processing.
946
+ let colWithoutAlias =
947
+ explicitAlias ? col.replace(aliasRegex, "").trim() : col;
948
+
949
+ // Use regex to extract the table and column names.
950
+ // This regex matches optional backticks around each identifier.
951
+ let tableName = this.type;
952
+ let columnName = "";
953
+ const columnRegex = /^`?([\w]+)`?\.`?([\w]+)`?$/;
954
+ const match = colWithoutAlias.match(columnRegex);
955
+ if (match) {
956
+ tableName = match[1];
957
+ columnName = match[2];
958
+ } else {
959
+ // If no table prefix is found, use the column name only.
960
+ columnName = colWithoutAlias.replace(/`/g, "");
961
+ }
962
+
963
+ // Generate the alias.
964
+ // If the column comes from a table different from this.type, then the alias
965
+ // becomes tableName_columnName. Otherwise, just use the column name.
966
+ let newAlias = columnName;
967
+ if (tableName && tableName !== this.type) {
968
+ newAlias = `${tableName}_${columnName}`;
969
+
970
+ // If an explicit alias was provided, favor that.
971
+ if (explicitAlias) {
972
+ newAlias = explicitAlias;
973
+ }
974
+
975
+ // Avoid duplicates by appending a count if needed.
976
+ if (aliasTracker.hasOwnProperty(newAlias)) {
977
+ aliasTracker[newAlias]++;
978
+ newAlias = `${newAlias}_${aliasTracker[newAlias]}`;
979
+ } else {
980
+ aliasTracker[newAlias] = 0;
981
+ }
982
+
983
+ // If there's no explicit alias, add an alias clause.
984
+ if (!explicitAlias) {
985
+ return `${colWithoutAlias} AS \`${newAlias}\``;
986
+ }
987
+ }
988
+ return col;
989
+ });
990
+
991
+ return fixedColumns.join(", ");
992
+ }
993
+
902
994
  async list(args = {}) {
903
995
  try {
904
996
  args.columns = this.prepColumns(args.columns);
997
+
905
998
  if (args.mapping_dept) this[_mapping_dept] = args.mapping_dept;
906
999
 
907
1000
  const nestings = [
@@ -910,6 +1003,8 @@ export default class SpiceModel {
910
1003
  ...this.extractNestings(args?.sort, this.type),
911
1004
  ];
912
1005
 
1006
+ args.columns = this.fixColumnName(args.columns);
1007
+
913
1008
  const mappedNestings = _.compact(
914
1009
  _.uniq(nestings).map((nesting) => {
915
1010
  const prop = this.props[nesting];
@@ -930,11 +1025,11 @@ export default class SpiceModel {
930
1025
  if (args.is_full_text === "true" || args.is_custom_query === "true") {
931
1026
  query = args.query;
932
1027
  } 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)`;
1028
+ query =
1029
+ args.filters ? this.makeQueryFromFilter(args.filters)
1030
+ : args.query ?
1031
+ `${args.query} AND (\`${this.type}\`.deleted = false OR \`${this.type}\`.deleted IS MISSING)`
1032
+ : `(\`${this.type}\`.deleted = false OR \`${this.type}\`.deleted IS MISSING)`;
938
1033
  }
939
1034
 
940
1035
  if (hasSQLInjection(query)) {
@@ -943,13 +1038,14 @@ export default class SpiceModel {
943
1038
 
944
1039
  args.limit = Number(args.limit) || undefined;
945
1040
  args.offset = Number(args.offset) || 0;
946
- args.sort = args.sort
947
- ? args.sort
1041
+ args.sort =
1042
+ args.sort ?
1043
+ args.sort
948
1044
  .split(",")
949
1045
  .map((item) =>
950
- item.includes(".")
951
- ? item
952
- : `\`${this.type}\`.${this.formatSortComponent(item)}`
1046
+ item.includes(".") ? item : (
1047
+ `\`${this.type}\`.${this.formatSortComponent(item)}`
1048
+ )
953
1049
  )
954
1050
  .join(",")
955
1051
  : `\`${this.type}\`.created_at DESC`;
@@ -969,14 +1065,15 @@ export default class SpiceModel {
969
1065
  );
970
1066
  results = cachedResults?.value;
971
1067
 
972
- if (!results || (await this.shouldForceRefresh(cachedResults))) {
973
- 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
- );
979
- }
1068
+ if (cachedResults?.value === undefined)
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
+ }
980
1077
  } else {
981
1078
  results = await this.fetchResults(args, query);
982
1079
  }
@@ -991,6 +1088,8 @@ export default class SpiceModel {
991
1088
  );
992
1089
  }
993
1090
 
1091
+ if (this.type == "user") console.log("results", results.data);
1092
+
994
1093
  if (args.skip_hooks !== true) {
995
1094
  await this.run_hook(results.data, "list", "after");
996
1095
  }
@@ -1238,9 +1337,9 @@ export default class SpiceModel {
1238
1337
  execute: async (data) => {
1239
1338
  return await this.mapToObject(
1240
1339
  data,
1241
- _.isString(properties[i].map.reference)
1242
- ? spice.models[properties[i].map.reference]
1243
- : properties[i].map.reference,
1340
+ _.isString(properties[i].map.reference) ?
1341
+ spice.models[properties[i].map.reference]
1342
+ : properties[i].map.reference,
1244
1343
  i,
1245
1344
  properties[i].map.destination || i,
1246
1345
  properties[i]
@@ -1256,9 +1355,9 @@ export default class SpiceModel {
1256
1355
  execute: async (data) => {
1257
1356
  return await this.mapToObjectArray(
1258
1357
  data,
1259
- _.isString(properties[i].map.reference)
1260
- ? spice.models[properties[i].map.reference]
1261
- : properties[i].map.reference,
1358
+ _.isString(properties[i].map.reference) ?
1359
+ spice.models[properties[i].map.reference]
1360
+ : properties[i].map.reference,
1262
1361
  i,
1263
1362
  properties[i].map.destination || i,
1264
1363
  properties[i]
@@ -1278,76 +1377,71 @@ export default class SpiceModel {
1278
1377
  }
1279
1378
  }
1280
1379
 
1281
- async do_serialize(data, type, old_data, args, path_to_be_removed) {
1282
- //console.log("CTX INside Model DO", this[_ctx]);
1380
+ async do_serialize(data, type, old_data, args, path_to_be_removed = []) {
1283
1381
  try {
1284
- // run serializers
1382
+ // Early exit if serialization should not run.
1383
+ if (!this.shouldSerializerRun(args, type)) {
1384
+ return data;
1385
+ }
1285
1386
 
1286
- if (!path_to_be_removed) {
1287
- path_to_be_removed = [];
1387
+ // Add external modifiers only once.
1388
+ if (this.type && !this[_external_modifier_loaded]) {
1389
+ this.addExternalModifiers(this.type);
1390
+ this[_external_modifier_loaded] = true;
1288
1391
  }
1289
1392
 
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
- }
1393
+ // Cache the modifiers lookup for the specified type.
1394
+ const modifiers = this[_serializers]?.[type]?.modifiers || [];
1395
+ for (const modifier of modifiers) {
1396
+ try {
1397
+ data = await modifier(data, old_data, this[_ctx], this.type);
1398
+ } catch (error) {
1399
+ console.error("Modifier error in do_serialize:", error.stack);
1305
1400
  }
1401
+ }
1306
1402
 
1307
- // make data an array
1308
- let original_is_array = _.isArray(data);
1403
+ // Ensure data is always an array for consistent processing.
1404
+ const originalIsArray = Array.isArray(data);
1405
+ if (!originalIsArray) {
1406
+ data = [data];
1407
+ }
1309
1408
 
1310
- if (!original_is_array) {
1311
- data = Array.of(data);
1409
+ // Compute the defaults from properties using reduce.
1410
+ const defaults = Object.keys(this.props).reduce((acc, key) => {
1411
+ const def = this.props[key]?.defaults?.[type];
1412
+ if (def !== undefined) {
1413
+ acc[key] =
1414
+ _.isFunction(def) ?
1415
+ def({ old_data: data, new_data: old_data })
1416
+ : def;
1312
1417
  }
1418
+ return acc;
1419
+ }, {});
1313
1420
 
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);
1333
- }
1334
-
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
- }
1421
+ // Merge defaults into each object.
1422
+ data = data.map((item) => _.defaults(item, defaults));
1345
1423
 
1346
- return original_is_array ? data : _.first(data);
1424
+ // If type is "read", clean the data by omitting certain props.
1425
+ if (type === "read") {
1426
+ // Collect hidden properties from schema.
1427
+ const hiddenProps = Object.keys(this.props).filter(
1428
+ (key) => this.props[key]?.hide
1429
+ );
1430
+ // Combine default props to remove.
1431
+ const propsToClean = [
1432
+ "deleted",
1433
+ "type",
1434
+ ...path_to_be_removed,
1435
+ ...hiddenProps,
1436
+ ];
1437
+ data = data.map((item) => _.omit(item, propsToClean));
1347
1438
  }
1348
- } catch (e) {
1349
- console.log(e.stack);
1350
- throw e;
1439
+
1440
+ // Return in the original format (array or single object).
1441
+ return originalIsArray ? data : data[0];
1442
+ } catch (error) {
1443
+ console.error("Error in do_serialize:", error.stack);
1444
+ throw error;
1351
1445
  }
1352
1446
  }
1353
1447
  }