spice-js 2.7.8 → 2.7.10

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.
@@ -31,6 +31,7 @@ var SDate = require("sonover-date"),
31
31
  _props = Symbol(),
32
32
  _args = Symbol(),
33
33
  _hooks = Symbol(),
34
+ _columns = Symbol(),
34
35
  _disable_lifecycle_events = Symbol(),
35
36
  _external_modifier_loaded = Symbol(),
36
37
  _skip_cache = Symbol(),
@@ -60,7 +61,7 @@ class SpiceModel {
60
61
  }
61
62
 
62
63
  try {
63
- var _args2, _args2$args, _args3, _args3$args, _args$args, _args4, _args4$args, _args5, _args5$args, _args6, _args6$args, _args7, _args7$args;
64
+ var _args2, _args2$args, _args3, _args3$args, _args$args, _args4, _args4$args, _args5, _args5$args, _args6, _args6$args, _args7, _args7$args, _args8, _args8$args;
64
65
 
65
66
  var dbtype = spice.config.database.connections[args.connection].type || "couchbase";
66
67
 
@@ -77,6 +78,7 @@ class SpiceModel {
77
78
  this[_skip_cache] = ((_args5 = args) == null ? void 0 : (_args5$args = _args5.args) == null ? void 0 : _args5$args.skip_cache) || false;
78
79
  this[_level] = ((_args6 = args) == null ? void 0 : (_args6$args = _args6.args) == null ? void 0 : _args6$args._level) || 0;
79
80
  this[_current_path] = ((_args7 = args) == null ? void 0 : (_args7$args = _args7.args) == null ? void 0 : _args7$args._current_path) || "";
81
+ this[_columns] = ((_args8 = args) == null ? void 0 : (_args8$args = _args8.args) == null ? void 0 : _args8$args._columns) || "";
80
82
  this[_hooks] = {
81
83
  create: {
82
84
  before: [],
@@ -608,13 +610,38 @@ class SpiceModel {
608
610
  }
609
611
 
610
612
  if (_.isString(args.id)) {
613
+ var _args9;
614
+
611
615
  if (args.skip_hooks !== true) {
612
616
  yield _this4.run_hook(args, "get", "before");
613
617
  } // ⚡ Include columns in cache key if specified
614
618
 
615
619
 
616
620
  var key = "get::" + _this4.type + "::" + args.id + (args.columns ? "::" + args.columns : "");
617
- var results = {};
621
+ var results = {}; // Extract nestings from columns if specified
622
+
623
+ var nestings = _this4.extractNestings(((_args9 = args) == null ? void 0 : _args9.columns) || "", _this4.type); // Build join metadata and prepare columns
624
+
625
+
626
+ _this4.buildJoinMetadata(nestings, args);
627
+
628
+ var columns = args.columns;
629
+ var columnArray = [];
630
+
631
+ if (columns) {
632
+ columns.split(',').forEach(col => {
633
+ var parts = col.trim().split("."); // remove the first segment
634
+ // Also match if parts[0] is source_property or '`' + source_property + '`'
635
+
636
+ var firstPart = parts[0]; // Remove backticks if present
637
+
638
+ var cleanFirstPart = firstPart && firstPart.startsWith('`') && firstPart.endsWith('`') ? firstPart.slice(1, -1) : firstPart; //if (parts.length > 0 && (firstPart === source_property || cleanFirstPart === source_property)) {
639
+ // Use cleanFirstPart for the map key to handle both cases consistently
640
+ // Remove backticks from each column segment, if present, before joining
641
+
642
+ columnArray.push(parts.slice(1).map(segment => segment && segment.startsWith('`') && segment.endsWith('`') ? segment.slice(1, -1) : segment).join(".")); // }
643
+ });
644
+ }
618
645
 
619
646
  if (_this4.shouldUseCache(_this4.type)) {
620
647
  // Retrieve the cached results
@@ -622,7 +649,7 @@ class SpiceModel {
622
649
 
623
650
  var isCacheEmpty = (cached_results == null ? void 0 : cached_results.value) === undefined; // Check if the cached result should be refreshed
624
651
 
625
- var shouldRefresh = yield _this4.shouldForceRefresh(cached_results); // Force a refresh for "workflow" type if needed
652
+ var shouldRefresh = yield _this4.shouldForceRefresh(cached_results);
626
653
 
627
654
  if (isCacheEmpty || shouldRefresh) {
628
655
  // Retrieve from the database and update cache
@@ -630,10 +657,14 @@ class SpiceModel {
630
657
  results = yield p.track(_this4.type + ".get.database",
631
658
  /*#__PURE__*/
632
659
  _asyncToGenerator(function* () {
633
- return yield _this4.database.get(args.id);
660
+ return yield _this4.database.get(args.id, {
661
+ columns: columnArray
662
+ });
634
663
  }));
635
664
  } else {
636
- results = yield _this4.database.get(args.id);
665
+ results = yield _this4.database.get(args.id, {
666
+ columns: columnArray
667
+ });
637
668
  }
638
669
 
639
670
  yield _this4.getCacheProviderObject(_this4.type).set(key, {
@@ -650,10 +681,14 @@ class SpiceModel {
650
681
  results = yield p.track(_this4.type + ".get.database",
651
682
  /*#__PURE__*/
652
683
  _asyncToGenerator(function* () {
653
- return yield _this4.database.get(args.id);
684
+ return yield _this4.database.get(args.id, {
685
+ columns: columnArray
686
+ });
654
687
  }));
655
688
  } else {
656
- results = yield _this4.database.get(args.id);
689
+ results = yield _this4.database.get(args.id, {
690
+ columns: columnArray
691
+ });
657
692
  }
658
693
  }
659
694
 
@@ -694,10 +729,10 @@ class SpiceModel {
694
729
 
695
730
  try {
696
731
  if (p) {
697
- var _args8;
732
+ var _args10;
698
733
 
699
734
  return yield p.track(_this4.type + ".get", doGet, {
700
- id: (_args8 = args) == null ? void 0 : _args8.id
735
+ id: (_args10 = args) == null ? void 0 : _args10.id
701
736
  });
702
737
  }
703
738
 
@@ -745,8 +780,7 @@ class SpiceModel {
745
780
 
746
781
  _.remove(args.ids, o => o == undefined);
747
782
 
748
- var key = "multi-get::" + _this6.type + "::" + _.join(args.ids, "|");
749
-
783
+ var key = "multi-get::" + _this6.type + "::" + _.join(args.ids, "|") + ":" + args.columns;
750
784
  var results = [];
751
785
 
752
786
  if (args.ids.length > 0) {
@@ -755,7 +789,10 @@ class SpiceModel {
755
789
  results = cached_results == null ? void 0 : cached_results.value;
756
790
 
757
791
  if ((cached_results == null ? void 0 : cached_results.value) === undefined || (yield _this6.shouldForceRefresh(cached_results))) {
758
- results = yield _this6.database.multi_get(args.ids, true);
792
+ results = yield _this6.database.multi_get(args.ids, {
793
+ keep_type: true,
794
+ columns: args.columns.length > 0 && args.columns != "" ? [...args.columns, 'type'] : []
795
+ });
759
796
 
760
797
  _this6.getCacheProviderObject(_this6.type).set(key, {
761
798
  value: results,
@@ -763,7 +800,10 @@ class SpiceModel {
763
800
  }, _this6.getCacheConfig(_this6.type));
764
801
  }
765
802
  } else {
766
- results = yield _this6.database.multi_get(args.ids, true);
803
+ results = yield _this6.database.multi_get(args.ids, {
804
+ keep_type: true,
805
+ columns: args.columns.length > 0 && args.columns != "" ? [...args.columns, 'type'] : []
806
+ });
767
807
  }
768
808
  }
769
809
 
@@ -787,10 +827,10 @@ class SpiceModel {
787
827
 
788
828
  try {
789
829
  if (p) {
790
- var _args9, _args9$ids;
830
+ var _args11, _args11$ids;
791
831
 
792
832
  return yield p.track(_this6.type + ".getMulti", doGetMulti, {
793
- ids_count: ((_args9 = args) == null ? void 0 : (_args9$ids = _args9.ids) == null ? void 0 : _args9$ids.length) || 0
833
+ ids_count: ((_args11 = args) == null ? void 0 : (_args11$ids = _args11.ids) == null ? void 0 : _args11$ids.length) || 0
794
834
  });
795
835
  }
796
836
 
@@ -1164,6 +1204,14 @@ class SpiceModel {
1164
1204
  var _qualified = q(alias) + "." + q(field);
1165
1205
 
1166
1206
  return explicitAs ? _qualified + " AS " + q(explicitAs) : _qualified;
1207
+ } // Check if alias has a map - if so, simplify to root column only (raw ID)
1208
+
1209
+
1210
+ var prop = this.props[alias];
1211
+
1212
+ if (prop == null ? void 0 : prop.map) {
1213
+ // Return base table qualified root column, ignoring the .field part
1214
+ return q(this.type) + "." + q(alias);
1167
1215
  }
1168
1216
 
1169
1217
  if (arraySet.has(alias)) {
@@ -1207,8 +1255,9 @@ class SpiceModel {
1207
1255
  }
1208
1256
 
1209
1257
  return col;
1210
- });
1211
- return _.join(_.compact(out), ",");
1258
+ }); // Deduplicate columns (e.g., when multiple mapped dot-notations share the same root)
1259
+
1260
+ return _.join(_.uniq(_.compact(out)), ",");
1212
1261
  }
1213
1262
 
1214
1263
  filterResultsByColumns(data, columns) {
@@ -1241,24 +1290,33 @@ class SpiceModel {
1241
1290
  }
1242
1291
 
1243
1292
  extractNestings(string, localType) {
1244
- var returnVal = [];
1293
+ var returnVal = []; // First, extract loop variables and embedded arrays from ANY/EVERY expressions
1294
+ // These should be EXCLUDED from join candidates
1295
+
1296
+ var anyEveryRegex = /(ANY|EVERY)\s+(\w+)\s+IN\s+`?(\w+)`?/gi;
1297
+ var anyEveryMatch;
1298
+ var loopVariables = new Set();
1299
+ var embeddedArrayFields = new Set();
1300
+
1301
+ while ((anyEveryMatch = anyEveryRegex.exec(string)) !== null) {
1302
+ loopVariables.add(anyEveryMatch[2]); // e.g., "p"
1303
+
1304
+ embeddedArrayFields.add(anyEveryMatch[3]); // e.g., "permissions"
1305
+ } // Now extract dot notation patterns, but skip loop variables and embedded arrays
1306
+
1307
+
1245
1308
  var regex = /(`?\w+`?)\.(`?\w+`?)/g;
1246
1309
  var match;
1247
1310
 
1248
1311
  while ((match = regex.exec(string)) !== null) {
1249
- var first = match[1].replace(/`/g, "");
1312
+ var first = match[1].replace(/`/g, ""); // Skip if it's the local type, a loop variable from ANY/EVERY, or an embedded array field
1250
1313
 
1251
- if (first !== localType) {
1314
+ if (first !== localType && !loopVariables.has(first) && !embeddedArrayFields.has(first)) {
1252
1315
  returnVal.push(first);
1253
1316
  }
1254
- }
1317
+ } // DO NOT add embedded array fields from ANY/EVERY expressions
1318
+ // They iterate over document's embedded array, not join to another collection
1255
1319
 
1256
- var queryRegex = /(ANY|EVERY)\s+\w+\s+IN\s+`?(\w+)`?/g;
1257
- var queryMatch;
1258
-
1259
- while ((queryMatch = queryRegex.exec(string)) !== null) {
1260
- returnVal.push(queryMatch[2]);
1261
- }
1262
1320
 
1263
1321
  return [...new Set(returnVal)];
1264
1322
  }
@@ -1286,6 +1344,55 @@ class SpiceModel {
1286
1344
  parts[0] = "`" + parts[0] + "`";
1287
1345
  return parts.join(" ");
1288
1346
  }
1347
+ /**
1348
+ * Builds join metadata from nestings for SQL joins and prepares columns.
1349
+ * @param {string[]} nestings - Array of alias names extracted from query/columns/sort
1350
+ * @param {Object} args - Arguments object containing columns (will be mutated with prepared columns)
1351
+ * @returns {Object} - { mappedNestings, protectedAliases, arrayAliases }
1352
+ */
1353
+
1354
+
1355
+ buildJoinMetadata(nestings, args) {
1356
+ var _args$columns;
1357
+
1358
+ // Decide which aliases we can join: only when map.type===MODEL AND reference is a STRING keyspace.
1359
+ var mappedNestings = _.compact(_.uniq(nestings).map(alias => {
1360
+ var prop = this.props[alias];
1361
+ if (!(prop == null ? void 0 : prop.map) || prop.map.type !== _2.MapType.MODEL) return null;
1362
+ var ref = prop.map.reference;
1363
+
1364
+ if (typeof ref !== "string") {
1365
+ // reference is a class/function/array-of-classes → no SQL join; serializer will handle it
1366
+ return null;
1367
+ }
1368
+
1369
+ var is_array = prop.type === "array" || prop.type === Array || prop.type === _2.DataType.ARRAY;
1370
+ return {
1371
+ alias,
1372
+ reference: ref.toLowerCase(),
1373
+ // keyspace to join
1374
+ is_array,
1375
+ type: prop.type,
1376
+ value_field: prop.map.value_field,
1377
+ destination: prop.map.destination || alias
1378
+ };
1379
+ }));
1380
+
1381
+ var protectedAliases = mappedNestings.map(m => m.alias);
1382
+ var arrayAliases = mappedNestings.filter(m => m.is_array).map(m => m.alias); // Columns: first prepare (prefix base table, rewrite array alias.field → ARRAY proj),
1383
+ // then normalize names and add default aliases
1384
+ //console.log("Columns in BuildJoinMetadata", args.columns);
1385
+
1386
+ this[_columns] = (_args$columns = args.columns) != null ? _args$columns : '';
1387
+ args.columns = this.prepColumns(args.columns, protectedAliases, arrayAliases);
1388
+ args.columns = this.fixColumnName(args.columns, protectedAliases); //console.log("Columns in BuildJoinMetadata after fixColumnName", args.columns);
1389
+
1390
+ return {
1391
+ mappedNestings,
1392
+ protectedAliases,
1393
+ arrayAliases
1394
+ };
1395
+ }
1289
1396
  /* removeSpaceAndSpecialCharacters(str) {
1290
1397
  return str.replace(/[^a-zA-Z0-9]/g, "");
1291
1398
  } */
@@ -1331,21 +1438,26 @@ class SpiceModel {
1331
1438
 
1332
1439
 
1333
1440
  if (tableName && tableName !== this.type) {
1334
- var newAlias = explicitAlias || tableName + "_" + columnName;
1441
+ var newAlias = explicitAlias || "" + tableName;
1335
1442
 
1336
1443
  if (!explicitAlias) {
1337
1444
  if (aliasTracker.hasOwnProperty(newAlias)) {
1338
1445
  aliasTracker[newAlias]++;
1339
- newAlias = newAlias + "_" + aliasTracker[newAlias];
1446
+ newAlias = "" + newAlias;
1340
1447
  } else {
1341
1448
  aliasTracker[newAlias] = 0;
1342
1449
  }
1343
1450
 
1344
1451
  return colWithoutAlias + " AS `" + newAlias + "`";
1345
1452
  }
1453
+ } // If column is already qualified with base table (e.g., `user`.`field`), return as-is
1454
+
1455
+
1456
+ if (tableName === this.type && match) {
1457
+ return col;
1346
1458
  }
1347
1459
 
1348
- return col;
1460
+ return "`" + col + "`";
1349
1461
  });
1350
1462
  return _.join(_.compact(out), ", ");
1351
1463
  }
@@ -1367,41 +1479,18 @@ class SpiceModel {
1367
1479
  /*#__PURE__*/
1368
1480
  function () {
1369
1481
  var _ref13 = _asyncToGenerator(function* () {
1370
- var _args10, _args11, _args12;
1482
+ var _args12, _args13;
1371
1483
 
1372
1484
  if (args.mapping_dept) _this12[_mapping_dept] = args.mapping_dept;
1373
1485
  if (args.mapping_dept_exempt) _this12[_mapping_dept_exempt] = args.mapping_dept_exempt; // Find alias tokens from query/columns/sort
1374
1486
 
1375
- var nestings = [..._this12.extractNestings(((_args10 = args) == null ? void 0 : _args10.query) || "", _this12.type), ..._this12.extractNestings(((_args11 = args) == null ? void 0 : _args11.columns) || "", _this12.type), ..._this12.extractNestings(((_args12 = args) == null ? void 0 : _args12.sort) || "", _this12.type)]; // Decide which aliases we can join: only when map.type===MODEL AND reference is a STRING keyspace.
1376
-
1377
- var mappedNestings = _.compact(_.uniq(nestings).map(alias => {
1378
- var prop = _this12.props[alias];
1379
- if (!(prop == null ? void 0 : prop.map) || prop.map.type !== _2.MapType.MODEL) return null;
1380
- var ref = prop.map.reference;
1381
-
1382
- if (typeof ref !== "string") {
1383
- // reference is a class/function/array-of-classes → no SQL join; serializer will handle it
1384
- return null;
1385
- }
1386
-
1387
- var is_array = prop.type === "array" || prop.type === Array || prop.type === _2.DataType.ARRAY;
1388
- return {
1389
- alias,
1390
- reference: ref.toLowerCase(),
1391
- // keyspace to join
1392
- is_array,
1393
- type: prop.type,
1394
- value_field: prop.map.value_field,
1395
- destination: prop.map.destination || alias
1396
- };
1397
- }));
1487
+ var nestings = [..._this12.extractNestings(((_args12 = args) == null ? void 0 : _args12.query) || "", _this12.type), //...this.extractNestings(args?.columns || "", this.type),
1488
+ ..._this12.extractNestings(((_args13 = args) == null ? void 0 : _args13.sort) || "", _this12.type)]; // Build join metadata and prepare columns
1398
1489
 
1399
- var protectedAliases = mappedNestings.map(m => m.alias);
1400
- var arrayAliases = mappedNestings.filter(m => m.is_array).map(m => m.alias); // Columns: first prepare (prefix base table, rewrite array alias.field → ARRAY proj),
1401
- // then normalize names and add default aliases
1490
+ var {
1491
+ mappedNestings
1492
+ } = _this12.buildJoinMetadata(nestings, args); // Build JOIN/NEST from the mapped keyspaces
1402
1493
 
1403
- args.columns = _this12.prepColumns(args.columns, protectedAliases, arrayAliases);
1404
- args.columns = _this12.fixColumnName(args.columns, protectedAliases); // Build JOIN/NEST from the mapped keyspaces
1405
1494
 
1406
1495
  args._join = _this12.createJoinSection(mappedNestings); // WHERE
1407
1496
 
@@ -1469,11 +1558,11 @@ class SpiceModel {
1469
1558
 
1470
1559
  try {
1471
1560
  if (p) {
1472
- var _args13, _args14;
1561
+ var _args14, _args15;
1473
1562
 
1474
1563
  return yield p.track(_this12.type + ".list", doList, {
1475
- limit: (_args13 = args) == null ? void 0 : _args13.limit,
1476
- offset: (_args14 = args) == null ? void 0 : _args14.offset
1564
+ limit: (_args14 = args) == null ? void 0 : _args14.limit,
1565
+ offset: (_args15 = args) == null ? void 0 : _args15.offset
1477
1566
  });
1478
1567
  }
1479
1568
 
@@ -1608,14 +1697,34 @@ class SpiceModel {
1608
1697
  });
1609
1698
  }
1610
1699
 
1611
- mapToObject(data, Class, source_property, store_property, property) {
1700
+ mapToObject(data, Class, source_property, store_property, property, args, type, mapping_dept, level, columns) {
1612
1701
  var _this15 = this;
1613
1702
 
1614
1703
  return _asyncToGenerator(function* () {
1615
1704
  var _this15$_ctx;
1616
1705
 
1617
1706
  // ⚡ Get profiler for proper async context forking
1618
- var p = (_this15$_ctx = _this15[_ctx]) == null ? void 0 : _this15$_ctx.profiler;
1707
+ var p = (_this15$_ctx = _this15[_ctx]) == null ? void 0 : _this15$_ctx.profiler; // create a array of all columns in args.columns skipping the first_segment of the Column string and pull out all the columns of where the new first segment matches source_property
1708
+ // Create a set of columns where the first segment matches source_property
1709
+
1710
+ var columnArray = [];
1711
+
1712
+ if (columns) {
1713
+ columns.split(',').forEach(col => {
1714
+ var parts = col.trim().split("."); // remove the first segment
1715
+ // Also match if parts[0] is source_property or '`' + source_property + '`'
1716
+
1717
+ var firstPart = parts[0]; // Remove backticks if present
1718
+
1719
+ var cleanFirstPart = firstPart && firstPart.startsWith('`') && firstPart.endsWith('`') ? firstPart.slice(1, -1) : firstPart;
1720
+
1721
+ if (parts.length > 0 && (firstPart === source_property || cleanFirstPart === source_property)) {
1722
+ // Use cleanFirstPart for the map key to handle both cases consistently
1723
+ // Remove backticks from each column segment, if present, before joining
1724
+ columnArray.push(parts.slice(1).map(segment => segment && segment.startsWith('`') && segment.endsWith('`') ? segment.slice(1, -1) : segment).join("."));
1725
+ }
1726
+ });
1727
+ }
1619
1728
 
1620
1729
  var original_is_array = _.isArray(data);
1621
1730
 
@@ -1631,8 +1740,18 @@ class SpiceModel {
1631
1740
  var ids = [];
1632
1741
 
1633
1742
  _.each(data, result => {
1634
- if (_.isString(result[source_property]) && result[source_property] != "") {
1635
- ids = _.union(ids, [result[source_property]]);
1743
+ var value = result[source_property]; // Check if value is in the empty string key (column aliasing issue)
1744
+
1745
+ if ((value === undefined || _.isObject(value) && _.isEmpty(value)) && result[''] && result[''][source_property]) {
1746
+ value = result[''][source_property];
1747
+ }
1748
+
1749
+ if (_.isString(value) && value != "") {
1750
+ // Value is a raw ID string
1751
+ ids = _.union(ids, [value]);
1752
+ } else if (_.isObject(value) && _.isString(value.id) && value.id != "") {
1753
+ // Value is already a joined object with an id field
1754
+ ids = _.union(ids, [value.id]);
1636
1755
  }
1637
1756
  }); // Build the path for child models
1638
1757
 
@@ -1644,15 +1763,18 @@ class SpiceModel {
1644
1763
  function () {
1645
1764
  var _ref16 = _asyncToGenerator(function* () {
1646
1765
  return yield Promise.allSettled(_.map(classes, obj => {
1647
- return new obj(_extends({}, _this15[_args], {
1766
+ var objInstance = new obj(_extends({}, _this15[_args], {
1648
1767
  skip_cache: _this15[_skip_cache],
1649
1768
  _level: _this15[_level] + 1,
1650
1769
  mapping_dept: _this15[_mapping_dept],
1651
1770
  mapping_dept_exempt: _this15[_mapping_dept_exempt],
1771
+ _columns: columnArray.join(','),
1652
1772
  _current_path: childPath
1653
- })).getMulti({
1773
+ }));
1774
+ return objInstance.getMulti({
1654
1775
  skip_hooks: true,
1655
- ids: ids
1776
+ ids: ids,
1777
+ columns: columnArray
1656
1778
  });
1657
1779
  }));
1658
1780
  });
@@ -1673,12 +1795,26 @@ class SpiceModel {
1673
1795
  }
1674
1796
 
1675
1797
  var ug = _.flatten(_.compact(_.map(returned_all, returned_obj => {
1676
- if (returned_obj.status == "fulfilled") return returned_obj.value;
1798
+ if (returned_obj.status == "fulfilled") {
1799
+ return returned_obj.value;
1800
+ }
1677
1801
  })));
1802
+ /* if(source_property == "group") {
1803
+ console.log("Returned All", source_property, store_property, ug);
1804
+ } */
1805
+
1678
1806
 
1679
1807
  data = _.map(data, result => {
1808
+ var sourceValue = result[source_property]; // Check if value is in the empty string key (column aliasing issue)
1809
+
1810
+ if ((sourceValue === undefined || _.isObject(sourceValue) && _.isEmpty(sourceValue)) && result[''] && result[''][source_property]) {
1811
+ sourceValue = result[''][source_property];
1812
+ } // Get the ID to match against - either a string ID or the id property of an object
1813
+
1814
+
1815
+ var sourceId = _.isString(sourceValue) ? sourceValue : _.isObject(sourceValue) ? sourceValue.id : null;
1680
1816
  var result_found = _.find(ug, g => {
1681
- return g.id == result[source_property];
1817
+ return g.id == sourceId;
1682
1818
  }) || {};
1683
1819
  result[store_property] = result_found;
1684
1820
  return result;
@@ -1689,7 +1825,7 @@ class SpiceModel {
1689
1825
  })();
1690
1826
  }
1691
1827
 
1692
- mapToObjectArray(data, Class, source_property, store_property, property) {
1828
+ mapToObjectArray(data, Class, source_property, store_property, property, args, type, mapping_dept, level, columns) {
1693
1829
  var _this16 = this;
1694
1830
 
1695
1831
  return _asyncToGenerator(function* () {
@@ -1697,6 +1833,24 @@ class SpiceModel {
1697
1833
 
1698
1834
  // ⚡ Get profiler for proper async context forking
1699
1835
  var p = (_this16$_ctx = _this16[_ctx]) == null ? void 0 : _this16$_ctx.profiler;
1836
+ var columnArray = [];
1837
+
1838
+ if (columns) {
1839
+ columns.split(',').forEach(col => {
1840
+ var parts = col.trim().split("."); // remove the first segment
1841
+ // Also match if parts[0] is source_property or '`' + source_property + '`'
1842
+
1843
+ var firstPart = parts[0]; // Remove backticks if present
1844
+
1845
+ var cleanFirstPart = firstPart && firstPart.startsWith('`') && firstPart.endsWith('`') ? firstPart.slice(1, -1) : firstPart;
1846
+
1847
+ if (parts.length > 0 && (firstPart === source_property || cleanFirstPart === source_property)) {
1848
+ // Use cleanFirstPart for the map key to handle both cases consistently
1849
+ // Remove backticks from each column segment, if present, before joining
1850
+ columnArray.push(parts.slice(1).map(segment => segment && segment.startsWith('`') && segment.endsWith('`') ? segment.slice(1, -1) : segment).join("."));
1851
+ }
1852
+ });
1853
+ }
1700
1854
 
1701
1855
  var original_is_array = _.isArray(data);
1702
1856
 
@@ -1718,9 +1872,18 @@ class SpiceModel {
1718
1872
 
1719
1873
  if (_.isString(result[source_property])) {
1720
1874
  value = [result[source_property]];
1721
- }
1875
+ } // Extract IDs - handle both string IDs and objects with id property
1876
+
1722
1877
 
1723
- var items = _.filter(value, obj => _.isString(obj) && obj != "");
1878
+ var items = _.compact(_.map(value, obj => {
1879
+ if (_.isString(obj) && obj != "") {
1880
+ return obj;
1881
+ } else if (_.isObject(obj) && _.isString(obj.id) && obj.id != "") {
1882
+ return obj.id;
1883
+ }
1884
+
1885
+ return null;
1886
+ }));
1724
1887
 
1725
1888
  ids = _.union(ids, items);
1726
1889
  }); // Build the path for child models
@@ -1741,10 +1904,12 @@ class SpiceModel {
1741
1904
  _level: _this16[_level] + 1,
1742
1905
  mapping_dept: _this16[_mapping_dept],
1743
1906
  mapping_dept_exempt: _this16[_mapping_dept_exempt],
1907
+ _columns: columnArray.join(','),
1744
1908
  _current_path: childPath
1745
1909
  })).getMulti({
1746
1910
  skip_hooks: true,
1747
- ids: ids
1911
+ ids: ids,
1912
+ columns: columnArray
1748
1913
  });
1749
1914
  }));
1750
1915
  });
@@ -1778,7 +1943,11 @@ class SpiceModel {
1778
1943
  return;
1779
1944
  }
1780
1945
 
1781
- result[store_property] = _.map(result[source_property], pid => _.find(returned_objects, p => p.id === pid));
1946
+ result[store_property] = _.map(result[source_property], item => {
1947
+ // Get the ID to match - either a string ID or the id property of an object
1948
+ var itemId = _.isString(item) ? item : _.isObject(item) ? item.id : null;
1949
+ return _.find(returned_objects, p => p.id === itemId);
1950
+ });
1782
1951
  result[store_property] = _.reject(result[store_property], obj => obj === null || obj === undefined);
1783
1952
  });
1784
1953
  }
@@ -1814,6 +1983,34 @@ class SpiceModel {
1814
1983
  that.addModifier(modifier);
1815
1984
  });
1816
1985
  }
1986
+ /**
1987
+ * Checks if a field is present in the columns string.
1988
+ * Used to skip modifier execution when the field isn't requested.
1989
+ * @param {string} fieldName - The field name to check for
1990
+ * @param {string} columns - Comma-separated column string
1991
+ * @returns {boolean} - True if field is in columns or columns is empty (fetch all)
1992
+ */
1993
+
1994
+
1995
+ isFieldInColumns(fieldName, columns) {
1996
+ // No columns filter = include all
1997
+ if (!columns || columns === '') return true;
1998
+ var tokens = columns.split(',');
1999
+
2000
+ for (var col of tokens) {
2001
+ var trimmed = col.trim(); // Check if this column starts with the field name (exact match or nested like "fieldName.subfield")
2002
+
2003
+ var firstPart = trimmed.split('.')[0]; // Handle backtick-wrapped column names
2004
+
2005
+ var cleanFirstPart = firstPart.startsWith('`') && firstPart.endsWith('`') ? firstPart.slice(1, -1) : firstPart;
2006
+
2007
+ if (cleanFirstPart === fieldName) {
2008
+ return true;
2009
+ }
2010
+ }
2011
+
2012
+ return false;
2013
+ }
1817
2014
 
1818
2015
  createMofifier(properties) {
1819
2016
  var _this17 = this;
@@ -1830,11 +2027,16 @@ class SpiceModel {
1830
2027
  _this17.addModifier({
1831
2028
  when: properties[i].map.when || "read",
1832
2029
  execute: function () {
1833
- var _execute = _asyncToGenerator(function* (data) {
1834
- return yield _this17.mapToObject(data, _.isString(properties[i].map.reference) ? spice.models[properties[i].map.reference] : properties[i].map.reference, i, properties[i].map.destination || i, properties[i]);
2030
+ var _execute = _asyncToGenerator(function* (data, old_data, ctx, type, args, mapping_dept, level, columns) {
2031
+ // Skip if columns are specified but this field isn't included
2032
+ if (!_this17.isFieldInColumns(i, columns)) {
2033
+ return data;
2034
+ }
2035
+
2036
+ return yield _this17.mapToObject(data, _.isString(properties[i].map.reference) ? spice.models[properties[i].map.reference] : properties[i].map.reference, i, properties[i].map.destination || i, properties[i], args, type, mapping_dept, level, columns);
1835
2037
  });
1836
2038
 
1837
- function execute(_x) {
2039
+ function execute(_x, _x2, _x3, _x4, _x5, _x6, _x7, _x8) {
1838
2040
  return _execute.apply(this, arguments);
1839
2041
  }
1840
2042
 
@@ -1851,11 +2053,16 @@ class SpiceModel {
1851
2053
  _this17.addModifier({
1852
2054
  when: properties[i].map.when || "read",
1853
2055
  execute: function () {
1854
- var _execute2 = _asyncToGenerator(function* (data) {
1855
- return yield _this17.mapToObjectArray(data, _.isString(properties[i].map.reference) ? spice.models[properties[i].map.reference] : properties[i].map.reference, i, properties[i].map.destination || i, properties[i]);
2056
+ var _execute2 = _asyncToGenerator(function* (data, old_data, ctx, type, args, mapping_dept, level, columns) {
2057
+ // Skip if columns are specified but this field isn't included
2058
+ if (!_this17.isFieldInColumns(i, columns)) {
2059
+ return data;
2060
+ }
2061
+
2062
+ return yield _this17.mapToObjectArray(data, _.isString(properties[i].map.reference) ? spice.models[properties[i].map.reference] : properties[i].map.reference, i, properties[i].map.destination || i, properties[i], args, type, mapping_dept, level, columns);
1856
2063
  });
1857
2064
 
1858
- function execute(_x2) {
2065
+ function execute(_x9, _x10, _x11, _x12, _x13, _x14, _x15, _x16) {
1859
2066
  return _execute2.apply(this, arguments);
1860
2067
  }
1861
2068
 
@@ -1921,7 +2128,7 @@ class SpiceModel {
1921
2128
  var modifier = modifiers[i];
1922
2129
 
1923
2130
  try {
1924
- var result = yield modifier(data, old_data, _this18[_ctx], _this18.type); // Guard against modifiers that return undefined
2131
+ var result = yield modifier(data, old_data, _this18[_ctx], _this18.type, args, _this18[_mapping_dept], _this18[_level], _this18[_columns]); // Guard against modifiers that return undefined
1925
2132
 
1926
2133
  if (result !== undefined) {
1927
2134
  data = result;
@@ -1966,7 +2173,7 @@ class SpiceModel {
1966
2173
  return (_this18$props$key2 = _this18.props[key]) == null ? void 0 : _this18$props$key2.hide;
1967
2174
  }); // Combine default props to remove.
1968
2175
 
1969
- var propsToClean = ["deleted", "type", ...path_to_be_removed, ...hiddenProps];
2176
+ var propsToClean = ["deleted", "type", "collection", ...path_to_be_removed, ...hiddenProps];
1970
2177
  data = data.map(item => _.omit(item, propsToClean));
1971
2178
  } // Return in the original format (array or single object).
1972
2179
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spice-js",
3
- "version": "2.7.8",
3
+ "version": "2.7.10",
4
4
  "description": "spice",
5
5
  "main": "build/index.js",
6
6
  "repository": {
@@ -15,6 +15,7 @@ var SDate = require("sonover-date"),
15
15
  _props = Symbol(),
16
16
  _args = Symbol(),
17
17
  _hooks = Symbol(),
18
+ _columns = Symbol(),
18
19
  _disable_lifecycle_events = Symbol(),
19
20
  _external_modifier_loaded = Symbol(),
20
21
  _skip_cache = Symbol(),
@@ -62,6 +63,7 @@ export default class SpiceModel {
62
63
  this[_skip_cache] = args?.args?.skip_cache || false;
63
64
  this[_level] = args?.args?._level || 0;
64
65
  this[_current_path] = args?.args?._current_path || "";
66
+ this[_columns] = args?.args?._columns || "";
65
67
  this[_hooks] = {
66
68
  create: {
67
69
  before: [],
@@ -528,6 +530,41 @@ export default class SpiceModel {
528
530
  let key = `get::${this.type}::${args.id}${args.columns ? `::${args.columns}` : ""}`;
529
531
  let results = {};
530
532
 
533
+ // Extract nestings from columns if specified
534
+ const nestings = this.extractNestings(args?.columns || "", this.type);
535
+
536
+ // Build join metadata and prepare columns
537
+ this.buildJoinMetadata(nestings, args);
538
+ let columns = args.columns;
539
+ let columnArray = [];
540
+ if (columns) {
541
+ columns.split(',').forEach(col => {
542
+
543
+ const parts = col.trim().split(".")// remove the first segment
544
+ // Also match if parts[0] is source_property or '`' + source_property + '`'
545
+ const firstPart = parts[0];
546
+ // Remove backticks if present
547
+ const cleanFirstPart = firstPart && firstPart.startsWith('`') && firstPart.endsWith('`')
548
+ ? firstPart.slice(1, -1)
549
+ : firstPart;
550
+
551
+ //if (parts.length > 0 && (firstPart === source_property || cleanFirstPart === source_property)) {
552
+ // Use cleanFirstPart for the map key to handle both cases consistently
553
+ // Remove backticks from each column segment, if present, before joining
554
+ columnArray.push(
555
+ parts
556
+ .slice(1)
557
+ .map(segment =>
558
+ segment && segment.startsWith('`') && segment.endsWith('`')
559
+ ? segment.slice(1, -1)
560
+ : segment
561
+ )
562
+ .join(".")
563
+ );
564
+ // }
565
+ });
566
+ }
567
+
531
568
  if (this.shouldUseCache(this.type)) {
532
569
  // Retrieve the cached results
533
570
  const cached_results = await this.getCacheProviderObject(
@@ -537,16 +574,15 @@ export default class SpiceModel {
537
574
  const isCacheEmpty = cached_results?.value === undefined;
538
575
  // Check if the cached result should be refreshed
539
576
  const shouldRefresh = await this.shouldForceRefresh(cached_results);
540
- // Force a refresh for "workflow" type if needed
541
577
 
542
578
  if (isCacheEmpty || shouldRefresh) {
543
579
  // Retrieve from the database and update cache
544
580
  if (p) {
545
581
  results = await p.track(`${this.type}.get.database`, async () => {
546
- return await this.database.get(args.id);
582
+ return await this.database.get(args.id, { columns: columnArray });
547
583
  });
548
584
  } else {
549
- results = await this.database.get(args.id);
585
+ results = await this.database.get(args.id, { columns: columnArray });
550
586
  }
551
587
  await this.getCacheProviderObject(this.type).set(
552
588
  key,
@@ -561,10 +597,10 @@ export default class SpiceModel {
561
597
  // Directly fetch from the database if caching is disabled
562
598
  if (p) {
563
599
  results = await p.track(`${this.type}.get.database`, async () => {
564
- return await this.database.get(args.id);
600
+ return await this.database.get(args.id, { columns: columnArray });
565
601
  });
566
602
  } else {
567
- results = await this.database.get(args.id);
603
+ results = await this.database.get(args.id, { columns: columnArray });
568
604
  }
569
605
  }
570
606
 
@@ -636,7 +672,7 @@ export default class SpiceModel {
636
672
  await this.run_hook(this, "list", "before");
637
673
  }
638
674
  _.remove(args.ids, (o) => o == undefined);
639
- let key = `multi-get::${this.type}::${_.join(args.ids, "|")}`;
675
+ let key = `multi-get::${this.type}::${_.join(args.ids, "|")}:${args.columns}`;
640
676
  let results = [];
641
677
  if (args.ids.length > 0) {
642
678
  if (this.shouldUseCache(this.type)) {
@@ -648,7 +684,7 @@ export default class SpiceModel {
648
684
  cached_results?.value === undefined ||
649
685
  (await this.shouldForceRefresh(cached_results))
650
686
  ) {
651
- results = await this.database.multi_get(args.ids, true);
687
+ results = await this.database.multi_get(args.ids, {keep_type:true, columns:args.columns.length > 0 && args.columns != ""?[...args.columns, 'type']:[]});
652
688
  this.getCacheProviderObject(this.type).set(
653
689
  key,
654
690
  { value: results, time: new Date().getTime() },
@@ -656,7 +692,7 @@ export default class SpiceModel {
656
692
  );
657
693
  }
658
694
  } else {
659
- results = await this.database.multi_get(args.ids, true);
695
+ results = await this.database.multi_get(args.ids, {keep_type:true, columns:args.columns.length> 0 && args.columns != ""?[...args.columns, 'type']:[]});
660
696
  }
661
697
  }
662
698
  _.remove(results, (o) => o.type != this.type);
@@ -668,11 +704,11 @@ export default class SpiceModel {
668
704
  args,
669
705
  await this.propsToBeRemoved(results)
670
706
  );
671
- }
707
+ }
672
708
 
673
709
  if (args.skip_hooks != true) {
674
710
  await this.run_hook(results, "list", "after", args.context);
675
- }
711
+ }
676
712
 
677
713
  return results;
678
714
  };
@@ -981,6 +1017,13 @@ export default class SpiceModel {
981
1017
  return explicitAs ? `${qualified} AS ${q(explicitAs)}` : qualified;
982
1018
  }
983
1019
 
1020
+ // Check if alias has a map - if so, simplify to root column only (raw ID)
1021
+ const prop = this.props[alias];
1022
+ if (prop?.map) {
1023
+ // Return base table qualified root column, ignoring the .field part
1024
+ return `${q(this.type)}.${q(alias)}`;
1025
+ }
1026
+
984
1027
  if (arraySet.has(alias)) {
985
1028
  let proj = this.buildArrayProjection(alias, field);
986
1029
  if (explicitAs && explicitAs !== `${alias}_${field}`) {
@@ -1027,7 +1070,8 @@ export default class SpiceModel {
1027
1070
  return col;
1028
1071
  });
1029
1072
 
1030
- return _.join(_.compact(out), ",");
1073
+ // Deduplicate columns (e.g., when multiple mapped dot-notations share the same root)
1074
+ return _.join(_.uniq(_.compact(out)), ",");
1031
1075
  }
1032
1076
 
1033
1077
  filterResultsByColumns(data, columns) {
@@ -1065,22 +1109,33 @@ export default class SpiceModel {
1065
1109
 
1066
1110
  extractNestings(string, localType) {
1067
1111
  let returnVal = [];
1112
+
1113
+ // First, extract loop variables and embedded arrays from ANY/EVERY expressions
1114
+ // These should be EXCLUDED from join candidates
1115
+ let anyEveryRegex = /(ANY|EVERY)\s+(\w+)\s+IN\s+`?(\w+)`?/gi;
1116
+ let anyEveryMatch;
1117
+ const loopVariables = new Set();
1118
+ const embeddedArrayFields = new Set();
1119
+
1120
+ while ((anyEveryMatch = anyEveryRegex.exec(string)) !== null) {
1121
+ loopVariables.add(anyEveryMatch[2]); // e.g., "p"
1122
+ embeddedArrayFields.add(anyEveryMatch[3]); // e.g., "permissions"
1123
+ }
1124
+
1125
+ // Now extract dot notation patterns, but skip loop variables and embedded arrays
1068
1126
  let regex = /(`?\w+`?)\.(`?\w+`?)/g;
1069
1127
  let match;
1070
1128
 
1071
1129
  while ((match = regex.exec(string)) !== null) {
1072
1130
  let first = match[1].replace(/`/g, "");
1073
- if (first !== localType) {
1131
+ // Skip if it's the local type, a loop variable from ANY/EVERY, or an embedded array field
1132
+ if (first !== localType && !loopVariables.has(first) && !embeddedArrayFields.has(first)) {
1074
1133
  returnVal.push(first);
1075
1134
  }
1076
1135
  }
1077
1136
 
1078
- let queryRegex = /(ANY|EVERY)\s+\w+\s+IN\s+`?(\w+)`?/g;
1079
- let queryMatch;
1080
-
1081
- while ((queryMatch = queryRegex.exec(string)) !== null) {
1082
- returnVal.push(queryMatch[2]);
1083
- }
1137
+ // DO NOT add embedded array fields from ANY/EVERY expressions
1138
+ // They iterate over document's embedded array, not join to another collection
1084
1139
 
1085
1140
  return [...new Set(returnVal)];
1086
1141
  }
@@ -1106,6 +1161,61 @@ export default class SpiceModel {
1106
1161
  return parts.join(" ");
1107
1162
  }
1108
1163
 
1164
+ /**
1165
+ * Builds join metadata from nestings for SQL joins and prepares columns.
1166
+ * @param {string[]} nestings - Array of alias names extracted from query/columns/sort
1167
+ * @param {Object} args - Arguments object containing columns (will be mutated with prepared columns)
1168
+ * @returns {Object} - { mappedNestings, protectedAliases, arrayAliases }
1169
+ */
1170
+ buildJoinMetadata(nestings, args) {
1171
+ // Decide which aliases we can join: only when map.type===MODEL AND reference is a STRING keyspace.
1172
+ const mappedNestings = _.compact(
1173
+ _.uniq(nestings).map((alias) => {
1174
+ const prop = this.props[alias];
1175
+ if (!prop?.map || prop.map.type !== MapType.MODEL) return null;
1176
+
1177
+ const ref = prop.map.reference;
1178
+ if (typeof ref !== "string") {
1179
+ // reference is a class/function/array-of-classes → no SQL join; serializer will handle it
1180
+ return null;
1181
+ }
1182
+
1183
+ const is_array =
1184
+ prop.type === "array" ||
1185
+ prop.type === Array ||
1186
+ prop.type === DataType.ARRAY;
1187
+
1188
+ return {
1189
+ alias,
1190
+ reference: ref.toLowerCase(), // keyspace to join
1191
+ is_array,
1192
+ type: prop.type,
1193
+ value_field: prop.map.value_field,
1194
+ destination: prop.map.destination || alias,
1195
+ };
1196
+ })
1197
+ );
1198
+
1199
+ const protectedAliases = mappedNestings.map((m) => m.alias);
1200
+ const arrayAliases = mappedNestings
1201
+ .filter((m) => m.is_array)
1202
+ .map((m) => m.alias);
1203
+
1204
+ // Columns: first prepare (prefix base table, rewrite array alias.field → ARRAY proj),
1205
+ // then normalize names and add default aliases
1206
+ //console.log("Columns in BuildJoinMetadata", args.columns);
1207
+ this[_columns] = args.columns ?? '';
1208
+ args.columns = this.prepColumns(
1209
+ args.columns,
1210
+ protectedAliases,
1211
+ arrayAliases
1212
+ );
1213
+
1214
+ args.columns = this.fixColumnName(args.columns, protectedAliases);
1215
+ //console.log("Columns in BuildJoinMetadata after fixColumnName", args.columns);
1216
+ return { mappedNestings, protectedAliases, arrayAliases };
1217
+ }
1218
+
1109
1219
  /* removeSpaceAndSpecialCharacters(str) {
1110
1220
  return str.replace(/[^a-zA-Z0-9]/g, "");
1111
1221
  } */
@@ -1157,11 +1267,11 @@ export default class SpiceModel {
1157
1267
 
1158
1268
  // For joined table columns, add default alias <table_col> if none
1159
1269
  if (tableName && tableName !== this.type) {
1160
- let newAlias = explicitAlias || `${tableName}_${columnName}`;
1270
+ let newAlias = explicitAlias || `${tableName}`;
1161
1271
  if (!explicitAlias) {
1162
1272
  if (aliasTracker.hasOwnProperty(newAlias)) {
1163
1273
  aliasTracker[newAlias]++;
1164
- newAlias = `${newAlias}_${aliasTracker[newAlias]}`;
1274
+ newAlias = `${newAlias}`;
1165
1275
  } else {
1166
1276
  aliasTracker[newAlias] = 0;
1167
1277
  }
@@ -1169,7 +1279,12 @@ export default class SpiceModel {
1169
1279
  }
1170
1280
  }
1171
1281
 
1172
- return col;
1282
+ // If column is already qualified with base table (e.g., `user`.`field`), return as-is
1283
+ if (tableName === this.type && match) {
1284
+ return col;
1285
+ }
1286
+
1287
+ return `\`${col}\``;
1173
1288
  });
1174
1289
 
1175
1290
  return _.join(_.compact(out), ", ");
@@ -1187,52 +1302,13 @@ export default class SpiceModel {
1187
1302
  // Find alias tokens from query/columns/sort
1188
1303
  const nestings = [
1189
1304
  ...this.extractNestings(args?.query || "", this.type),
1190
- ...this.extractNestings(args?.columns || "", this.type),
1305
+ //...this.extractNestings(args?.columns || "", this.type),
1191
1306
  ...this.extractNestings(args?.sort || "", this.type),
1192
1307
  ];
1193
1308
 
1194
- // Decide which aliases we can join: only when map.type===MODEL AND reference is a STRING keyspace.
1195
- const mappedNestings = _.compact(
1196
- _.uniq(nestings).map((alias) => {
1197
- const prop = this.props[alias];
1198
- if (!prop?.map || prop.map.type !== MapType.MODEL) return null;
1199
-
1200
- const ref = prop.map.reference;
1201
- if (typeof ref !== "string") {
1202
- // reference is a class/function/array-of-classes → no SQL join; serializer will handle it
1203
- return null;
1204
- }
1205
-
1206
- const is_array =
1207
- prop.type === "array" ||
1208
- prop.type === Array ||
1209
- prop.type === DataType.ARRAY;
1210
-
1211
- return {
1212
- alias,
1213
- reference: ref.toLowerCase(), // keyspace to join
1214
- is_array,
1215
- type: prop.type,
1216
- value_field: prop.map.value_field,
1217
- destination: prop.map.destination || alias,
1218
- };
1219
- })
1220
- );
1221
-
1222
- const protectedAliases = mappedNestings.map((m) => m.alias);
1223
- const arrayAliases = mappedNestings
1224
- .filter((m) => m.is_array)
1225
- .map((m) => m.alias);
1226
-
1227
- // Columns: first prepare (prefix base table, rewrite array alias.field → ARRAY proj),
1228
- // then normalize names and add default aliases
1229
- args.columns = this.prepColumns(
1230
- args.columns,
1231
- protectedAliases,
1232
- arrayAliases
1233
- );
1234
- args.columns = this.fixColumnName(args.columns, protectedAliases);
1235
-
1309
+ // Build join metadata and prepare columns
1310
+ const { mappedNestings } = this.buildJoinMetadata(nestings, args);
1311
+
1236
1312
  // Build JOIN/NEST from the mapped keyspaces
1237
1313
  args._join = this.createJoinSection(mappedNestings);
1238
1314
 
@@ -1299,7 +1375,7 @@ export default class SpiceModel {
1299
1375
  "read",
1300
1376
  {},
1301
1377
  args,
1302
- await this.propsToBeRemoved(results.data)
1378
+ await this.propsToBeRemoved(results.data),
1303
1379
  );
1304
1380
  }
1305
1381
 
@@ -1434,10 +1510,39 @@ export default class SpiceModel {
1434
1510
  });
1435
1511
  }
1436
1512
 
1437
- async mapToObject(data, Class, source_property, store_property, property) {
1513
+ async mapToObject(data, Class, source_property, store_property, property, args, type, mapping_dept, level, columns) {
1438
1514
  // ⚡ Get profiler for proper async context forking
1439
1515
  const p = this[_ctx]?.profiler;
1440
-
1516
+ // create a array of all columns in args.columns skipping the first_segment of the Column string and pull out all the columns of where the new first segment matches source_property
1517
+ // Create a set of columns where the first segment matches source_property
1518
+ let columnArray = [];
1519
+ if (columns) {
1520
+ columns.split(',').forEach(col => {
1521
+
1522
+ const parts = col.trim().split(".")// remove the first segment
1523
+ // Also match if parts[0] is source_property or '`' + source_property + '`'
1524
+ const firstPart = parts[0];
1525
+ // Remove backticks if present
1526
+ const cleanFirstPart = firstPart && firstPart.startsWith('`') && firstPart.endsWith('`')
1527
+ ? firstPart.slice(1, -1)
1528
+ : firstPart;
1529
+
1530
+ if (parts.length > 0 && (firstPart === source_property || cleanFirstPart === source_property)) {
1531
+ // Use cleanFirstPart for the map key to handle both cases consistently
1532
+ // Remove backticks from each column segment, if present, before joining
1533
+ columnArray.push(
1534
+ parts
1535
+ .slice(1)
1536
+ .map(segment =>
1537
+ segment && segment.startsWith('`') && segment.endsWith('`')
1538
+ ? segment.slice(1, -1)
1539
+ : segment
1540
+ )
1541
+ .join(".")
1542
+ );
1543
+ }
1544
+ });
1545
+ }
1441
1546
  let original_is_array = _.isArray(data);
1442
1547
  if (!original_is_array) {
1443
1548
  data = Array.of(data);
@@ -1448,11 +1553,19 @@ export default class SpiceModel {
1448
1553
 
1449
1554
  let ids = [];
1450
1555
  _.each(data, (result) => {
1451
- if (
1452
- _.isString(result[source_property]) &&
1453
- result[source_property] != ""
1454
- ) {
1455
- ids = _.union(ids, [result[source_property]]);
1556
+ let value = result[source_property];
1557
+
1558
+ // Check if value is in the empty string key (column aliasing issue)
1559
+ if ((value === undefined || (_.isObject(value) && _.isEmpty(value))) && result[''] && result[''][source_property]) {
1560
+ value = result[''][source_property];
1561
+ }
1562
+
1563
+ if (_.isString(value) && value != "") {
1564
+ // Value is a raw ID string
1565
+ ids = _.union(ids, [value]);
1566
+ } else if (_.isObject(value) && _.isString(value.id) && value.id != "") {
1567
+ // Value is already a joined object with an id field
1568
+ ids = _.union(ids, [value.id]);
1456
1569
  }
1457
1570
  });
1458
1571
 
@@ -1466,16 +1579,20 @@ export default class SpiceModel {
1466
1579
  const fetchRelated = async () => {
1467
1580
  return await Promise.allSettled(
1468
1581
  _.map(classes, (obj) => {
1469
- return new obj({
1582
+ let objInstance = new obj({
1470
1583
  ...this[_args],
1471
1584
  skip_cache: this[_skip_cache],
1472
1585
  _level: this[_level] + 1,
1473
1586
  mapping_dept: this[_mapping_dept],
1474
1587
  mapping_dept_exempt: this[_mapping_dept_exempt],
1588
+ _columns: columnArray.join(','),
1475
1589
  _current_path: childPath,
1476
- }).getMulti({
1590
+ })
1591
+
1592
+ return objInstance.getMulti({
1477
1593
  skip_hooks: true,
1478
1594
  ids: ids,
1595
+ columns:columnArray,
1479
1596
  });
1480
1597
  })
1481
1598
  );
@@ -1495,15 +1612,30 @@ export default class SpiceModel {
1495
1612
  let ug = _.flatten(
1496
1613
  _.compact(
1497
1614
  _.map(returned_all, (returned_obj) => {
1498
- if (returned_obj.status == "fulfilled") return returned_obj.value;
1615
+ if (returned_obj.status == "fulfilled") {
1616
+ return returned_obj.value;
1617
+ }
1499
1618
  })
1500
1619
  )
1501
1620
  );
1502
1621
 
1622
+ /* if(source_property == "group") {
1623
+ console.log("Returned All", source_property, store_property, ug);
1624
+ } */
1625
+
1503
1626
  data = _.map(data, (result) => {
1627
+ let sourceValue = result[source_property];
1628
+
1629
+ // Check if value is in the empty string key (column aliasing issue)
1630
+ if ((sourceValue === undefined || (_.isObject(sourceValue) && _.isEmpty(sourceValue))) && result[''] && result[''][source_property]) {
1631
+ sourceValue = result[''][source_property];
1632
+ }
1633
+
1634
+ // Get the ID to match against - either a string ID or the id property of an object
1635
+ const sourceId = _.isString(sourceValue) ? sourceValue : (_.isObject(sourceValue) ? sourceValue.id : null);
1504
1636
  let result_found =
1505
1637
  _.find(ug, (g) => {
1506
- return g.id == result[source_property];
1638
+ return g.id == sourceId;
1507
1639
  }) || {};
1508
1640
  result[store_property] = result_found;
1509
1641
  return result;
@@ -1517,11 +1649,41 @@ export default class SpiceModel {
1517
1649
  Class,
1518
1650
  source_property,
1519
1651
  store_property,
1520
- property
1652
+ property,
1653
+ args, type, mapping_dept, level, columns
1521
1654
  ) {
1522
1655
  // ⚡ Get profiler for proper async context forking
1523
1656
  const p = this[_ctx]?.profiler;
1524
1657
 
1658
+ let columnArray = [];
1659
+ if (columns) {
1660
+ columns.split(',').forEach(col => {
1661
+
1662
+ const parts = col.trim().split(".")// remove the first segment
1663
+ // Also match if parts[0] is source_property or '`' + source_property + '`'
1664
+ const firstPart = parts[0];
1665
+ // Remove backticks if present
1666
+ const cleanFirstPart = firstPart && firstPart.startsWith('`') && firstPart.endsWith('`')
1667
+ ? firstPart.slice(1, -1)
1668
+ : firstPart;
1669
+
1670
+ if (parts.length > 0 && (firstPart === source_property || cleanFirstPart === source_property)) {
1671
+ // Use cleanFirstPart for the map key to handle both cases consistently
1672
+ // Remove backticks from each column segment, if present, before joining
1673
+ columnArray.push(
1674
+ parts
1675
+ .slice(1)
1676
+ .map(segment =>
1677
+ segment && segment.startsWith('`') && segment.endsWith('`')
1678
+ ? segment.slice(1, -1)
1679
+ : segment
1680
+ )
1681
+ .join(".")
1682
+ );
1683
+ }
1684
+ });
1685
+ }
1686
+
1525
1687
  let original_is_array = _.isArray(data);
1526
1688
  if (!original_is_array) {
1527
1689
  data = Array.of(data);
@@ -1540,7 +1702,15 @@ export default class SpiceModel {
1540
1702
  value = [result[source_property]];
1541
1703
  }
1542
1704
 
1543
- let items = _.filter(value, (obj) => _.isString(obj) && obj != "");
1705
+ // Extract IDs - handle both string IDs and objects with id property
1706
+ let items = _.compact(_.map(value, (obj) => {
1707
+ if (_.isString(obj) && obj != "") {
1708
+ return obj;
1709
+ } else if (_.isObject(obj) && _.isString(obj.id) && obj.id != "") {
1710
+ return obj.id;
1711
+ }
1712
+ return null;
1713
+ }));
1544
1714
  ids = _.union(ids, items);
1545
1715
  });
1546
1716
 
@@ -1562,10 +1732,12 @@ export default class SpiceModel {
1562
1732
  _level: this[_level] + 1,
1563
1733
  mapping_dept: this[_mapping_dept],
1564
1734
  mapping_dept_exempt: this[_mapping_dept_exempt],
1735
+ _columns: columnArray.join(','),
1565
1736
  _current_path: childPath,
1566
1737
  }).getMulti({
1567
1738
  skip_hooks: true,
1568
1739
  ids: ids,
1740
+ columns:columnArray,
1569
1741
  });
1570
1742
  })
1571
1743
  );
@@ -1600,9 +1772,11 @@ export default class SpiceModel {
1600
1772
  return;
1601
1773
  }
1602
1774
 
1603
- result[store_property] = _.map(result[source_property], (pid) =>
1604
- _.find(returned_objects, (p) => p.id === pid)
1605
- );
1775
+ result[store_property] = _.map(result[source_property], (item) => {
1776
+ // Get the ID to match - either a string ID or the id property of an object
1777
+ const itemId = _.isString(item) ? item : (_.isObject(item) ? item.id : null);
1778
+ return _.find(returned_objects, (p) => p.id === itemId);
1779
+ });
1606
1780
  result[store_property] = _.reject(
1607
1781
  result[store_property],
1608
1782
  (obj) => obj === null || obj === undefined
@@ -1630,6 +1804,34 @@ export default class SpiceModel {
1630
1804
  });
1631
1805
  }
1632
1806
 
1807
+ /**
1808
+ * Checks if a field is present in the columns string.
1809
+ * Used to skip modifier execution when the field isn't requested.
1810
+ * @param {string} fieldName - The field name to check for
1811
+ * @param {string} columns - Comma-separated column string
1812
+ * @returns {boolean} - True if field is in columns or columns is empty (fetch all)
1813
+ */
1814
+ isFieldInColumns(fieldName, columns) {
1815
+ // No columns filter = include all
1816
+ if (!columns || columns === '') return true;
1817
+
1818
+ const tokens = columns.split(',');
1819
+ for (const col of tokens) {
1820
+ const trimmed = col.trim();
1821
+ // Check if this column starts with the field name (exact match or nested like "fieldName.subfield")
1822
+ const firstPart = trimmed.split('.')[0];
1823
+ // Handle backtick-wrapped column names
1824
+ const cleanFirstPart = firstPart.startsWith('`') && firstPart.endsWith('`')
1825
+ ? firstPart.slice(1, -1)
1826
+ : firstPart;
1827
+
1828
+ if (cleanFirstPart === fieldName) {
1829
+ return true;
1830
+ }
1831
+ }
1832
+ return false;
1833
+ }
1834
+
1633
1835
  createMofifier(properties) {
1634
1836
  for (let i in properties) {
1635
1837
  if (properties[i].map) {
@@ -1640,7 +1842,11 @@ export default class SpiceModel {
1640
1842
  case "string": {
1641
1843
  this.addModifier({
1642
1844
  when: properties[i].map.when || "read",
1643
- execute: async (data) => {
1845
+ execute: async (data, old_data, ctx, type, args, mapping_dept, level, columns) => {
1846
+ // Skip if columns are specified but this field isn't included
1847
+ if (!this.isFieldInColumns(i, columns)) {
1848
+ return data;
1849
+ }
1644
1850
  return await this.mapToObject(
1645
1851
  data,
1646
1852
  _.isString(properties[i].map.reference) ?
@@ -1648,7 +1854,12 @@ export default class SpiceModel {
1648
1854
  : properties[i].map.reference,
1649
1855
  i,
1650
1856
  properties[i].map.destination || i,
1651
- properties[i]
1857
+ properties[i],
1858
+ args,
1859
+ type,
1860
+ mapping_dept,
1861
+ level,
1862
+ columns
1652
1863
  );
1653
1864
  },
1654
1865
  });
@@ -1658,7 +1869,11 @@ export default class SpiceModel {
1658
1869
  case "array": {
1659
1870
  this.addModifier({
1660
1871
  when: properties[i].map.when || "read",
1661
- execute: async (data) => {
1872
+ execute: async (data, old_data, ctx, type, args, mapping_dept, level, columns) => {
1873
+ // Skip if columns are specified but this field isn't included
1874
+ if (!this.isFieldInColumns(i, columns)) {
1875
+ return data;
1876
+ }
1662
1877
  return await this.mapToObjectArray(
1663
1878
  data,
1664
1879
  _.isString(properties[i].map.reference) ?
@@ -1666,7 +1881,12 @@ export default class SpiceModel {
1666
1881
  : properties[i].map.reference,
1667
1882
  i,
1668
1883
  properties[i].map.destination || i,
1669
- properties[i]
1884
+ properties[i],
1885
+ args,
1886
+ type,
1887
+ mapping_dept,
1888
+ level,
1889
+ columns
1670
1890
  );
1671
1891
  },
1672
1892
  });
@@ -1676,7 +1896,7 @@ export default class SpiceModel {
1676
1896
  break;
1677
1897
  }
1678
1898
  case MapType.LOOKUP: {
1679
- break;
1899
+ break;
1680
1900
  }
1681
1901
  }
1682
1902
  }
@@ -1704,7 +1924,7 @@ export default class SpiceModel {
1704
1924
  for (let i = 0; i < modifiers.length; i++) {
1705
1925
  const modifier = modifiers[i];
1706
1926
  try {
1707
- const result = await modifier(data, old_data, this[_ctx], this.type);
1927
+ const result = await modifier(data, old_data, this[_ctx], this.type, args, this[_mapping_dept], this[_level], this[_columns]);
1708
1928
  // Guard against modifiers that return undefined
1709
1929
  if (result !== undefined) {
1710
1930
  data = result;
@@ -1754,6 +1974,7 @@ export default class SpiceModel {
1754
1974
  const propsToClean = [
1755
1975
  "deleted",
1756
1976
  "type",
1977
+ "collection",
1757
1978
  ...path_to_be_removed,
1758
1979
  ...hiddenProps,
1759
1980
  ];
package/, DELETED
File without changes