spice-js 2.7.8 → 2.7.9

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
- }
1255
-
1256
- var queryRegex = /(ANY|EVERY)\s+\w+\s+IN\s+`?(\w+)`?/g;
1257
- var queryMatch;
1317
+ } // DO NOT add embedded array fields from ANY/EVERY expressions
1318
+ // They iterate over document's embedded array, not join to another collection
1258
1319
 
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,53 @@ 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
+ // Decide which aliases we can join: only when map.type===MODEL AND reference is a STRING keyspace.
1357
+ var mappedNestings = _.compact(_.uniq(nestings).map(alias => {
1358
+ var prop = this.props[alias];
1359
+ if (!(prop == null ? void 0 : prop.map) || prop.map.type !== _2.MapType.MODEL) return null;
1360
+ var ref = prop.map.reference;
1361
+
1362
+ if (typeof ref !== "string") {
1363
+ // reference is a class/function/array-of-classes → no SQL join; serializer will handle it
1364
+ return null;
1365
+ }
1366
+
1367
+ var is_array = prop.type === "array" || prop.type === Array || prop.type === _2.DataType.ARRAY;
1368
+ return {
1369
+ alias,
1370
+ reference: ref.toLowerCase(),
1371
+ // keyspace to join
1372
+ is_array,
1373
+ type: prop.type,
1374
+ value_field: prop.map.value_field,
1375
+ destination: prop.map.destination || alias
1376
+ };
1377
+ }));
1378
+
1379
+ var protectedAliases = mappedNestings.map(m => m.alias);
1380
+ var arrayAliases = mappedNestings.filter(m => m.is_array).map(m => m.alias); // Columns: first prepare (prefix base table, rewrite array alias.field → ARRAY proj),
1381
+ // then normalize names and add default aliases
1382
+ //console.log("Columns in BuildJoinMetadata", args.columns);
1383
+
1384
+ this[_columns] = "" + args.columns;
1385
+ args.columns = this.prepColumns(args.columns, protectedAliases, arrayAliases);
1386
+ args.columns = this.fixColumnName(args.columns, protectedAliases); //console.log("Columns in BuildJoinMetadata after fixColumnName", args.columns);
1387
+
1388
+ return {
1389
+ mappedNestings,
1390
+ protectedAliases,
1391
+ arrayAliases
1392
+ };
1393
+ }
1289
1394
  /* removeSpaceAndSpecialCharacters(str) {
1290
1395
  return str.replace(/[^a-zA-Z0-9]/g, "");
1291
1396
  } */
@@ -1331,21 +1436,26 @@ class SpiceModel {
1331
1436
 
1332
1437
 
1333
1438
  if (tableName && tableName !== this.type) {
1334
- var newAlias = explicitAlias || tableName + "_" + columnName;
1439
+ var newAlias = explicitAlias || "" + tableName;
1335
1440
 
1336
1441
  if (!explicitAlias) {
1337
1442
  if (aliasTracker.hasOwnProperty(newAlias)) {
1338
1443
  aliasTracker[newAlias]++;
1339
- newAlias = newAlias + "_" + aliasTracker[newAlias];
1444
+ newAlias = "" + newAlias;
1340
1445
  } else {
1341
1446
  aliasTracker[newAlias] = 0;
1342
1447
  }
1343
1448
 
1344
1449
  return colWithoutAlias + " AS `" + newAlias + "`";
1345
1450
  }
1451
+ } // If column is already qualified with base table (e.g., `user`.`field`), return as-is
1452
+
1453
+
1454
+ if (tableName === this.type && match) {
1455
+ return col;
1346
1456
  }
1347
1457
 
1348
- return col;
1458
+ return "`" + col + "`";
1349
1459
  });
1350
1460
  return _.join(_.compact(out), ", ");
1351
1461
  }
@@ -1367,41 +1477,18 @@ class SpiceModel {
1367
1477
  /*#__PURE__*/
1368
1478
  function () {
1369
1479
  var _ref13 = _asyncToGenerator(function* () {
1370
- var _args10, _args11, _args12;
1480
+ var _args12, _args13;
1371
1481
 
1372
1482
  if (args.mapping_dept) _this12[_mapping_dept] = args.mapping_dept;
1373
1483
  if (args.mapping_dept_exempt) _this12[_mapping_dept_exempt] = args.mapping_dept_exempt; // Find alias tokens from query/columns/sort
1374
1484
 
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
- }));
1485
+ var nestings = [..._this12.extractNestings(((_args12 = args) == null ? void 0 : _args12.query) || "", _this12.type), //...this.extractNestings(args?.columns || "", this.type),
1486
+ ..._this12.extractNestings(((_args13 = args) == null ? void 0 : _args13.sort) || "", _this12.type)]; // Build join metadata and prepare columns
1398
1487
 
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
1488
+ var {
1489
+ mappedNestings
1490
+ } = _this12.buildJoinMetadata(nestings, args); // Build JOIN/NEST from the mapped keyspaces
1402
1491
 
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
1492
 
1406
1493
  args._join = _this12.createJoinSection(mappedNestings); // WHERE
1407
1494
 
@@ -1469,11 +1556,11 @@ class SpiceModel {
1469
1556
 
1470
1557
  try {
1471
1558
  if (p) {
1472
- var _args13, _args14;
1559
+ var _args14, _args15;
1473
1560
 
1474
1561
  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
1562
+ limit: (_args14 = args) == null ? void 0 : _args14.limit,
1563
+ offset: (_args15 = args) == null ? void 0 : _args15.offset
1477
1564
  });
1478
1565
  }
1479
1566
 
@@ -1608,14 +1695,34 @@ class SpiceModel {
1608
1695
  });
1609
1696
  }
1610
1697
 
1611
- mapToObject(data, Class, source_property, store_property, property) {
1698
+ mapToObject(data, Class, source_property, store_property, property, args, type, mapping_dept, level, columns) {
1612
1699
  var _this15 = this;
1613
1700
 
1614
1701
  return _asyncToGenerator(function* () {
1615
1702
  var _this15$_ctx;
1616
1703
 
1617
1704
  // ⚡ Get profiler for proper async context forking
1618
- var p = (_this15$_ctx = _this15[_ctx]) == null ? void 0 : _this15$_ctx.profiler;
1705
+ 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
1706
+ // Create a set of columns where the first segment matches source_property
1707
+
1708
+ var columnArray = [];
1709
+
1710
+ if (columns) {
1711
+ columns.split(',').forEach(col => {
1712
+ var parts = col.trim().split("."); // remove the first segment
1713
+ // Also match if parts[0] is source_property or '`' + source_property + '`'
1714
+
1715
+ var firstPart = parts[0]; // Remove backticks if present
1716
+
1717
+ var cleanFirstPart = firstPart && firstPart.startsWith('`') && firstPart.endsWith('`') ? firstPart.slice(1, -1) : firstPart;
1718
+
1719
+ if (parts.length > 0 && (firstPart === source_property || cleanFirstPart === source_property)) {
1720
+ // Use cleanFirstPart for the map key to handle both cases consistently
1721
+ // Remove backticks from each column segment, if present, before joining
1722
+ columnArray.push(parts.slice(1).map(segment => segment && segment.startsWith('`') && segment.endsWith('`') ? segment.slice(1, -1) : segment).join("."));
1723
+ }
1724
+ });
1725
+ }
1619
1726
 
1620
1727
  var original_is_array = _.isArray(data);
1621
1728
 
@@ -1631,8 +1738,18 @@ class SpiceModel {
1631
1738
  var ids = [];
1632
1739
 
1633
1740
  _.each(data, result => {
1634
- if (_.isString(result[source_property]) && result[source_property] != "") {
1635
- ids = _.union(ids, [result[source_property]]);
1741
+ var value = result[source_property]; // Check if value is in the empty string key (column aliasing issue)
1742
+
1743
+ if ((value === undefined || _.isObject(value) && _.isEmpty(value)) && result[''] && result[''][source_property]) {
1744
+ value = result[''][source_property];
1745
+ }
1746
+
1747
+ if (_.isString(value) && value != "") {
1748
+ // Value is a raw ID string
1749
+ ids = _.union(ids, [value]);
1750
+ } else if (_.isObject(value) && _.isString(value.id) && value.id != "") {
1751
+ // Value is already a joined object with an id field
1752
+ ids = _.union(ids, [value.id]);
1636
1753
  }
1637
1754
  }); // Build the path for child models
1638
1755
 
@@ -1644,15 +1761,18 @@ class SpiceModel {
1644
1761
  function () {
1645
1762
  var _ref16 = _asyncToGenerator(function* () {
1646
1763
  return yield Promise.allSettled(_.map(classes, obj => {
1647
- return new obj(_extends({}, _this15[_args], {
1764
+ var objInstance = new obj(_extends({}, _this15[_args], {
1648
1765
  skip_cache: _this15[_skip_cache],
1649
1766
  _level: _this15[_level] + 1,
1650
1767
  mapping_dept: _this15[_mapping_dept],
1651
1768
  mapping_dept_exempt: _this15[_mapping_dept_exempt],
1769
+ _columns: columnArray.join(','),
1652
1770
  _current_path: childPath
1653
- })).getMulti({
1771
+ }));
1772
+ return objInstance.getMulti({
1654
1773
  skip_hooks: true,
1655
- ids: ids
1774
+ ids: ids,
1775
+ columns: columnArray
1656
1776
  });
1657
1777
  }));
1658
1778
  });
@@ -1673,12 +1793,26 @@ class SpiceModel {
1673
1793
  }
1674
1794
 
1675
1795
  var ug = _.flatten(_.compact(_.map(returned_all, returned_obj => {
1676
- if (returned_obj.status == "fulfilled") return returned_obj.value;
1796
+ if (returned_obj.status == "fulfilled") {
1797
+ return returned_obj.value;
1798
+ }
1677
1799
  })));
1800
+ /* if(source_property == "group") {
1801
+ console.log("Returned All", source_property, store_property, ug);
1802
+ } */
1803
+
1678
1804
 
1679
1805
  data = _.map(data, result => {
1806
+ var sourceValue = result[source_property]; // Check if value is in the empty string key (column aliasing issue)
1807
+
1808
+ if ((sourceValue === undefined || _.isObject(sourceValue) && _.isEmpty(sourceValue)) && result[''] && result[''][source_property]) {
1809
+ sourceValue = result[''][source_property];
1810
+ } // Get the ID to match against - either a string ID or the id property of an object
1811
+
1812
+
1813
+ var sourceId = _.isString(sourceValue) ? sourceValue : _.isObject(sourceValue) ? sourceValue.id : null;
1680
1814
  var result_found = _.find(ug, g => {
1681
- return g.id == result[source_property];
1815
+ return g.id == sourceId;
1682
1816
  }) || {};
1683
1817
  result[store_property] = result_found;
1684
1818
  return result;
@@ -1689,7 +1823,7 @@ class SpiceModel {
1689
1823
  })();
1690
1824
  }
1691
1825
 
1692
- mapToObjectArray(data, Class, source_property, store_property, property) {
1826
+ mapToObjectArray(data, Class, source_property, store_property, property, args, type, mapping_dept, level, columns) {
1693
1827
  var _this16 = this;
1694
1828
 
1695
1829
  return _asyncToGenerator(function* () {
@@ -1697,6 +1831,24 @@ class SpiceModel {
1697
1831
 
1698
1832
  // ⚡ Get profiler for proper async context forking
1699
1833
  var p = (_this16$_ctx = _this16[_ctx]) == null ? void 0 : _this16$_ctx.profiler;
1834
+ var columnArray = [];
1835
+
1836
+ if (columns) {
1837
+ columns.split(',').forEach(col => {
1838
+ var parts = col.trim().split("."); // remove the first segment
1839
+ // Also match if parts[0] is source_property or '`' + source_property + '`'
1840
+
1841
+ var firstPart = parts[0]; // Remove backticks if present
1842
+
1843
+ var cleanFirstPart = firstPart && firstPart.startsWith('`') && firstPart.endsWith('`') ? firstPart.slice(1, -1) : firstPart;
1844
+
1845
+ if (parts.length > 0 && (firstPart === source_property || cleanFirstPart === source_property)) {
1846
+ // Use cleanFirstPart for the map key to handle both cases consistently
1847
+ // Remove backticks from each column segment, if present, before joining
1848
+ columnArray.push(parts.slice(1).map(segment => segment && segment.startsWith('`') && segment.endsWith('`') ? segment.slice(1, -1) : segment).join("."));
1849
+ }
1850
+ });
1851
+ }
1700
1852
 
1701
1853
  var original_is_array = _.isArray(data);
1702
1854
 
@@ -1718,9 +1870,18 @@ class SpiceModel {
1718
1870
 
1719
1871
  if (_.isString(result[source_property])) {
1720
1872
  value = [result[source_property]];
1721
- }
1873
+ } // Extract IDs - handle both string IDs and objects with id property
1722
1874
 
1723
- var items = _.filter(value, obj => _.isString(obj) && obj != "");
1875
+
1876
+ var items = _.compact(_.map(value, obj => {
1877
+ if (_.isString(obj) && obj != "") {
1878
+ return obj;
1879
+ } else if (_.isObject(obj) && _.isString(obj.id) && obj.id != "") {
1880
+ return obj.id;
1881
+ }
1882
+
1883
+ return null;
1884
+ }));
1724
1885
 
1725
1886
  ids = _.union(ids, items);
1726
1887
  }); // Build the path for child models
@@ -1741,10 +1902,12 @@ class SpiceModel {
1741
1902
  _level: _this16[_level] + 1,
1742
1903
  mapping_dept: _this16[_mapping_dept],
1743
1904
  mapping_dept_exempt: _this16[_mapping_dept_exempt],
1905
+ _columns: columnArray.join(','),
1744
1906
  _current_path: childPath
1745
1907
  })).getMulti({
1746
1908
  skip_hooks: true,
1747
- ids: ids
1909
+ ids: ids,
1910
+ columns: columnArray
1748
1911
  });
1749
1912
  }));
1750
1913
  });
@@ -1778,7 +1941,11 @@ class SpiceModel {
1778
1941
  return;
1779
1942
  }
1780
1943
 
1781
- result[store_property] = _.map(result[source_property], pid => _.find(returned_objects, p => p.id === pid));
1944
+ result[store_property] = _.map(result[source_property], item => {
1945
+ // Get the ID to match - either a string ID or the id property of an object
1946
+ var itemId = _.isString(item) ? item : _.isObject(item) ? item.id : null;
1947
+ return _.find(returned_objects, p => p.id === itemId);
1948
+ });
1782
1949
  result[store_property] = _.reject(result[store_property], obj => obj === null || obj === undefined);
1783
1950
  });
1784
1951
  }
@@ -1830,11 +1997,11 @@ class SpiceModel {
1830
1997
  _this17.addModifier({
1831
1998
  when: properties[i].map.when || "read",
1832
1999
  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]);
2000
+ var _execute = _asyncToGenerator(function* (data, old_data, ctx, type, args, mapping_dept, level, columns) {
2001
+ 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
2002
  });
1836
2003
 
1837
- function execute(_x) {
2004
+ function execute(_x, _x2, _x3, _x4, _x5, _x6, _x7, _x8) {
1838
2005
  return _execute.apply(this, arguments);
1839
2006
  }
1840
2007
 
@@ -1851,11 +2018,11 @@ class SpiceModel {
1851
2018
  _this17.addModifier({
1852
2019
  when: properties[i].map.when || "read",
1853
2020
  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]);
2021
+ var _execute2 = _asyncToGenerator(function* (data, old_data, ctx, type, args, mapping_dept, level, columns) {
2022
+ 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
2023
  });
1857
2024
 
1858
- function execute(_x2) {
2025
+ function execute(_x9, _x10, _x11, _x12, _x13, _x14, _x15, _x16) {
1859
2026
  return _execute2.apply(this, arguments);
1860
2027
  }
1861
2028
 
@@ -1921,7 +2088,7 @@ class SpiceModel {
1921
2088
  var modifier = modifiers[i];
1922
2089
 
1923
2090
  try {
1924
- var result = yield modifier(data, old_data, _this18[_ctx], _this18.type); // Guard against modifiers that return undefined
2091
+ 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
2092
 
1926
2093
  if (result !== undefined) {
1927
2094
  data = result;
@@ -1966,7 +2133,7 @@ class SpiceModel {
1966
2133
  return (_this18$props$key2 = _this18.props[key]) == null ? void 0 : _this18$props$key2.hide;
1967
2134
  }); // Combine default props to remove.
1968
2135
 
1969
- var propsToClean = ["deleted", "type", ...path_to_be_removed, ...hiddenProps];
2136
+ var propsToClean = ["deleted", "type", "collection", ...path_to_be_removed, ...hiddenProps];
1970
2137
  data = data.map(item => _.omit(item, propsToClean));
1971
2138
  } // Return in the original format (array or single object).
1972
2139
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spice-js",
3
- "version": "2.7.8",
3
+ "version": "2.7.9",
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
@@ -1640,7 +1814,7 @@ export default class SpiceModel {
1640
1814
  case "string": {
1641
1815
  this.addModifier({
1642
1816
  when: properties[i].map.when || "read",
1643
- execute: async (data) => {
1817
+ execute: async (data, old_data, ctx, type, args, mapping_dept, level, columns) => {
1644
1818
  return await this.mapToObject(
1645
1819
  data,
1646
1820
  _.isString(properties[i].map.reference) ?
@@ -1648,7 +1822,12 @@ export default class SpiceModel {
1648
1822
  : properties[i].map.reference,
1649
1823
  i,
1650
1824
  properties[i].map.destination || i,
1651
- properties[i]
1825
+ properties[i],
1826
+ args,
1827
+ type,
1828
+ mapping_dept,
1829
+ level,
1830
+ columns
1652
1831
  );
1653
1832
  },
1654
1833
  });
@@ -1658,7 +1837,7 @@ export default class SpiceModel {
1658
1837
  case "array": {
1659
1838
  this.addModifier({
1660
1839
  when: properties[i].map.when || "read",
1661
- execute: async (data) => {
1840
+ execute: async (data, old_data, ctx, type, args, mapping_dept, level, columns) => {
1662
1841
  return await this.mapToObjectArray(
1663
1842
  data,
1664
1843
  _.isString(properties[i].map.reference) ?
@@ -1666,7 +1845,12 @@ export default class SpiceModel {
1666
1845
  : properties[i].map.reference,
1667
1846
  i,
1668
1847
  properties[i].map.destination || i,
1669
- properties[i]
1848
+ properties[i],
1849
+ args,
1850
+ type,
1851
+ mapping_dept,
1852
+ level,
1853
+ columns
1670
1854
  );
1671
1855
  },
1672
1856
  });
@@ -1676,7 +1860,7 @@ export default class SpiceModel {
1676
1860
  break;
1677
1861
  }
1678
1862
  case MapType.LOOKUP: {
1679
- break;
1863
+ break;
1680
1864
  }
1681
1865
  }
1682
1866
  }
@@ -1704,7 +1888,7 @@ export default class SpiceModel {
1704
1888
  for (let i = 0; i < modifiers.length; i++) {
1705
1889
  const modifier = modifiers[i];
1706
1890
  try {
1707
- const result = await modifier(data, old_data, this[_ctx], this.type);
1891
+ const result = await modifier(data, old_data, this[_ctx], this.type, args, this[_mapping_dept], this[_level], this[_columns]);
1708
1892
  // Guard against modifiers that return undefined
1709
1893
  if (result !== undefined) {
1710
1894
  data = result;
@@ -1754,6 +1938,7 @@ export default class SpiceModel {
1754
1938
  const propsToClean = [
1755
1939
  "deleted",
1756
1940
  "type",
1941
+ "collection",
1757
1942
  ...path_to_be_removed,
1758
1943
  ...hiddenProps,
1759
1944
  ];
package/, DELETED
File without changes