spice-js 2.7.2 → 2.7.4

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.
@@ -54,7 +54,6 @@ if (!Promise.allSettled) {
54
54
  }
55
55
 
56
56
  class SpiceModel {
57
- // ⚡ Static caches for performance optimization
58
57
  constructor(args) {
59
58
  if (args === void 0) {
60
59
  args = {};
@@ -704,50 +703,75 @@ class SpiceModel {
704
703
  var _this6 = this;
705
704
 
706
705
  return _asyncToGenerator(function* () {
707
- try {
708
- if (!args) {
709
- args = {};
710
- }
706
+ var _this6$_ctx;
711
707
 
712
- if (args.skip_hooks != true) {
713
- yield _this6.run_hook(_this6, "list", "before");
714
- }
708
+ // Profiling: use track() for proper async context forking
709
+ var p = (_this6$_ctx = _this6[_ctx]) == null ? void 0 : _this6$_ctx.profiler;
710
+
711
+ var doGetMulti =
712
+ /*#__PURE__*/
713
+ function () {
714
+ var _ref5 = _asyncToGenerator(function* () {
715
+ if (!args) {
716
+ args = {};
717
+ }
718
+
719
+ if (args.skip_hooks != true) {
720
+ yield _this6.run_hook(_this6, "list", "before");
721
+ }
715
722
 
716
- _.remove(args.ids, o => o == undefined);
723
+ _.remove(args.ids, o => o == undefined);
717
724
 
718
- var key = "multi-get::" + _this6.type + "::" + _.join(args.ids, "|");
725
+ var key = "multi-get::" + _this6.type + "::" + _.join(args.ids, "|");
719
726
 
720
- var results = [];
727
+ var results = [];
721
728
 
722
- if (args.ids.length > 0) {
723
- if (_this6.shouldUseCache(_this6.type)) {
724
- var cached_results = yield _this6.getCacheProviderObject(_this6.type).get(key);
725
- results = cached_results == null ? void 0 : cached_results.value;
729
+ if (args.ids.length > 0) {
730
+ if (_this6.shouldUseCache(_this6.type)) {
731
+ var cached_results = yield _this6.getCacheProviderObject(_this6.type).get(key);
732
+ results = cached_results == null ? void 0 : cached_results.value;
726
733
 
727
- if ((cached_results == null ? void 0 : cached_results.value) === undefined || (yield _this6.shouldForceRefresh(cached_results))) {
728
- results = yield _this6.database.multi_get(args.ids, true);
734
+ if ((cached_results == null ? void 0 : cached_results.value) === undefined || (yield _this6.shouldForceRefresh(cached_results))) {
735
+ results = yield _this6.database.multi_get(args.ids, true);
729
736
 
730
- _this6.getCacheProviderObject(_this6.type).set(key, {
731
- value: results,
732
- time: new Date().getTime()
733
- }, _this6.getCacheConfig(_this6.type));
737
+ _this6.getCacheProviderObject(_this6.type).set(key, {
738
+ value: results,
739
+ time: new Date().getTime()
740
+ }, _this6.getCacheConfig(_this6.type));
741
+ }
742
+ } else {
743
+ results = yield _this6.database.multi_get(args.ids, true);
734
744
  }
735
- } else {
736
- results = yield _this6.database.multi_get(args.ids, true);
737
745
  }
738
- }
739
746
 
740
- _.remove(results, o => o.type != _this6.type);
747
+ _.remove(results, o => o.type != _this6.type);
741
748
 
742
- if (args.skip_read_serialize != true && args.skip_serialize != true) {
743
- results = yield _this6.do_serialize(results, "read", {}, args, (yield _this6.propsToBeRemoved(results)));
744
- }
749
+ if (args.skip_read_serialize != true && args.skip_serialize != true) {
750
+ results = yield _this6.do_serialize(results, "read", {}, args, (yield _this6.propsToBeRemoved(results)));
751
+ }
745
752
 
746
- if (args.skip_hooks != true) {
747
- yield _this6.run_hook(results, "list", "after", args.context);
753
+ if (args.skip_hooks != true) {
754
+ yield _this6.run_hook(results, "list", "after", args.context);
755
+ }
756
+
757
+ return results;
758
+ });
759
+
760
+ return function doGetMulti() {
761
+ return _ref5.apply(this, arguments);
762
+ };
763
+ }();
764
+
765
+ try {
766
+ if (p) {
767
+ var _args9, _args9$ids;
768
+
769
+ return yield p.track(_this6.type + ".getMulti", doGetMulti, {
770
+ ids_count: ((_args9 = args) == null ? void 0 : (_args9$ids = _args9.ids) == null ? void 0 : _args9$ids.length) || 0
771
+ });
748
772
  }
749
773
 
750
- return results;
774
+ return yield doGetMulti();
751
775
  } catch (e) {
752
776
  console.warn(e.stack);
753
777
  throw e;
@@ -796,7 +820,7 @@ class SpiceModel {
796
820
  var doUpdate =
797
821
  /*#__PURE__*/
798
822
  function () {
799
- var _ref5 = _asyncToGenerator(function* () {
823
+ var _ref6 = _asyncToGenerator(function* () {
800
824
  _this8.updated_at = new SDate().now();
801
825
  var results = yield _this8.database.get(args.id);
802
826
  var item_exist = yield _this8.exist(results);
@@ -855,7 +879,7 @@ class SpiceModel {
855
879
  });
856
880
 
857
881
  return function doUpdate() {
858
- return _ref5.apply(this, arguments);
882
+ return _ref6.apply(this, arguments);
859
883
  };
860
884
  }();
861
885
 
@@ -890,7 +914,7 @@ class SpiceModel {
890
914
  var doCreate =
891
915
  /*#__PURE__*/
892
916
  function () {
893
- var _ref7 = _asyncToGenerator(function* () {
917
+ var _ref8 = _asyncToGenerator(function* () {
894
918
  var form;
895
919
  _this9.created_at = new SDate().now();
896
920
  args = _.defaults(args, {
@@ -950,7 +974,7 @@ class SpiceModel {
950
974
  });
951
975
 
952
976
  return function doCreate() {
953
- return _ref7.apply(this, arguments);
977
+ return _ref8.apply(this, arguments);
954
978
  };
955
979
  }();
956
980
 
@@ -1000,7 +1024,7 @@ class SpiceModel {
1000
1024
  var doDelete =
1001
1025
  /*#__PURE__*/
1002
1026
  function () {
1003
- var _ref9 = _asyncToGenerator(function* () {
1027
+ var _ref10 = _asyncToGenerator(function* () {
1004
1028
  var item_exist = yield _this11.exist(args.id);
1005
1029
 
1006
1030
  if (!item_exist) {
@@ -1018,7 +1042,7 @@ class SpiceModel {
1018
1042
  var dbOperation =
1019
1043
  /*#__PURE__*/
1020
1044
  function () {
1021
- var _ref10 = _asyncToGenerator(function* () {
1045
+ var _ref11 = _asyncToGenerator(function* () {
1022
1046
  if (args.hard) {
1023
1047
  delete_response = yield _this11.database.delete(args.id);
1024
1048
 
@@ -1031,7 +1055,7 @@ class SpiceModel {
1031
1055
  });
1032
1056
 
1033
1057
  return function dbOperation() {
1034
- return _ref10.apply(this, arguments);
1058
+ return _ref11.apply(this, arguments);
1035
1059
  };
1036
1060
  }();
1037
1061
 
@@ -1049,7 +1073,7 @@ class SpiceModel {
1049
1073
  });
1050
1074
 
1051
1075
  return function doDelete() {
1052
- return _ref9.apply(this, arguments);
1076
+ return _ref10.apply(this, arguments);
1053
1077
  };
1054
1078
  }();
1055
1079
 
@@ -1218,12 +1242,12 @@ class SpiceModel {
1218
1242
 
1219
1243
  createJoinSection(mappedNestings) {
1220
1244
  if (!mappedNestings || mappedNestings.length === 0) return "";
1221
- return mappedNestings.map((_ref11) => {
1245
+ return mappedNestings.map((_ref12) => {
1222
1246
  var {
1223
1247
  alias,
1224
1248
  reference,
1225
1249
  is_array
1226
- } = _ref11;
1250
+ } = _ref12;
1227
1251
  var keyspace = (0, _fix.fixCollection)(reference);
1228
1252
 
1229
1253
  if (is_array === true) {
@@ -1319,13 +1343,13 @@ class SpiceModel {
1319
1343
  var doList =
1320
1344
  /*#__PURE__*/
1321
1345
  function () {
1322
- var _ref12 = _asyncToGenerator(function* () {
1323
- var _args9, _args10, _args11;
1346
+ var _ref13 = _asyncToGenerator(function* () {
1347
+ var _args10, _args11, _args12;
1324
1348
 
1325
1349
  if (args.mapping_dept) _this12[_mapping_dept] = args.mapping_dept;
1326
1350
  if (args.mapping_dept_exempt) _this12[_mapping_dept_exempt] = args.mapping_dept_exempt; // Find alias tokens from query/columns/sort
1327
1351
 
1328
- var nestings = [..._this12.extractNestings(((_args9 = args) == null ? void 0 : _args9.query) || "", _this12.type), ..._this12.extractNestings(((_args10 = args) == null ? void 0 : _args10.columns) || "", _this12.type), ..._this12.extractNestings(((_args11 = args) == null ? void 0 : _args11.sort) || "", _this12.type)]; // Decide which aliases we can join: only when map.type===MODEL AND reference is a STRING keyspace.
1352
+ 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.
1329
1353
 
1330
1354
  var mappedNestings = _.compact(_.uniq(nestings).map(alias => {
1331
1355
  var prop = _this12.props[alias];
@@ -1416,17 +1440,17 @@ class SpiceModel {
1416
1440
  });
1417
1441
 
1418
1442
  return function doList() {
1419
- return _ref12.apply(this, arguments);
1443
+ return _ref13.apply(this, arguments);
1420
1444
  };
1421
1445
  }();
1422
1446
 
1423
1447
  try {
1424
1448
  if (p) {
1425
- var _args12, _args13;
1449
+ var _args13, _args14;
1426
1450
 
1427
1451
  return yield p.track(_this12.type + ".list", doList, {
1428
- limit: (_args12 = args) == null ? void 0 : _args12.limit,
1429
- offset: (_args13 = args) == null ? void 0 : _args13.offset
1452
+ limit: (_args13 = args) == null ? void 0 : _args13.limit,
1453
+ offset: (_args14 = args) == null ? void 0 : _args14.offset
1430
1454
  });
1431
1455
  }
1432
1456
 
@@ -1450,7 +1474,7 @@ class SpiceModel {
1450
1474
  var doFetch =
1451
1475
  /*#__PURE__*/
1452
1476
  function () {
1453
- var _ref13 = _asyncToGenerator(function* () {
1477
+ var _ref14 = _asyncToGenerator(function* () {
1454
1478
  if (args.is_custom_query === "true" && args.ids.length > 0) {
1455
1479
  return yield _this13.database.query(query);
1456
1480
  } else if (args.is_full_text === "true") {
@@ -1462,7 +1486,7 @@ class SpiceModel {
1462
1486
  });
1463
1487
 
1464
1488
  return function doFetch() {
1465
- return _ref13.apply(this, arguments);
1489
+ return _ref14.apply(this, arguments);
1466
1490
  };
1467
1491
  }();
1468
1492
 
@@ -1474,12 +1498,12 @@ class SpiceModel {
1474
1498
  })();
1475
1499
  }
1476
1500
 
1477
- addHook(_ref14) {
1501
+ addHook(_ref15) {
1478
1502
  var {
1479
1503
  operation,
1480
1504
  when,
1481
1505
  execute
1482
- } = _ref14;
1506
+ } = _ref15;
1483
1507
 
1484
1508
  this[_hooks][operation][when].push(execute);
1485
1509
  }
@@ -1534,54 +1558,6 @@ class SpiceModel {
1534
1558
  }
1535
1559
 
1536
1560
  return true;
1537
- } // ⚡ OPTIMIZED: Cache defaults metadata per model type
1538
-
1539
-
1540
- getDefaultsMetadata(type) {
1541
- var cacheKey = this.type + "::" + type;
1542
-
1543
- if (!SpiceModel._defaultsCache[cacheKey]) {
1544
- var staticDefaults = {};
1545
- var dynamicDefaults = []; // Pre-compute once per model type
1546
-
1547
- for (var key in this.props) {
1548
- var _this$props$key, _this$props$key$defau;
1549
-
1550
- var def = (_this$props$key = this.props[key]) == null ? void 0 : (_this$props$key$defau = _this$props$key.defaults) == null ? void 0 : _this$props$key$defau[type];
1551
-
1552
- if (def !== undefined) {
1553
- if (_.isFunction(def)) {
1554
- dynamicDefaults.push({
1555
- key,
1556
- fn: def
1557
- });
1558
- } else {
1559
- staticDefaults[key] = def;
1560
- }
1561
- }
1562
- }
1563
-
1564
- SpiceModel._defaultsCache[cacheKey] = {
1565
- staticDefaults,
1566
- dynamicDefaults,
1567
- hasDefaults: Object.keys(staticDefaults).length > 0 || dynamicDefaults.length > 0
1568
- };
1569
- }
1570
-
1571
- return SpiceModel._defaultsCache[cacheKey];
1572
- } // ⚡ OPTIMIZED: Cache hidden props per model type
1573
-
1574
-
1575
- getHiddenProps() {
1576
- if (!SpiceModel._hiddenPropsCache[this.type]) {
1577
- SpiceModel._hiddenPropsCache[this.type] = Object.keys(this.props).filter(key => {
1578
- var _this$props$key2;
1579
-
1580
- return (_this$props$key2 = this.props[key]) == null ? void 0 : _this$props$key2.hide;
1581
- });
1582
- }
1583
-
1584
- return SpiceModel._hiddenPropsCache[this.type];
1585
1561
  } // Check if a field is exempt from mapping depth limits
1586
1562
  // Supports deep references like "group.permissions"
1587
1563
 
@@ -1613,6 +1589,11 @@ class SpiceModel {
1613
1589
  var _this15 = this;
1614
1590
 
1615
1591
  return _asyncToGenerator(function* () {
1592
+ var _this15$_ctx;
1593
+
1594
+ // ⚡ Get profiler for proper async context forking
1595
+ var p = (_this15$_ctx = _this15[_ctx]) == null ? void 0 : _this15$_ctx.profiler;
1596
+
1616
1597
  var original_is_array = _.isArray(data);
1617
1598
 
1618
1599
  if (!original_is_array) {
@@ -1633,19 +1614,40 @@ class SpiceModel {
1633
1614
  }); // Build the path for child models
1634
1615
 
1635
1616
 
1636
- var childPath = _this15[_current_path] ? _this15[_current_path] + "." + source_property : source_property;
1637
- var returned_all = yield Promise.allSettled(_.map(classes, obj => {
1638
- return new obj(_extends({}, _this15[_args], {
1639
- skip_cache: _this15[_skip_cache],
1640
- _level: _this15[_level] + 1,
1641
- mapping_dept: _this15[_mapping_dept],
1642
- mapping_dept_exempt: _this15[_mapping_dept_exempt],
1643
- _current_path: childPath
1644
- })).getMulti({
1645
- skip_hooks: true,
1646
- ids: ids
1617
+ var childPath = _this15[_current_path] ? _this15[_current_path] + "." + source_property : source_property; // ⚡ Wrap in profiler track() to ensure proper async context for child operations
1618
+
1619
+ var fetchRelated =
1620
+ /*#__PURE__*/
1621
+ function () {
1622
+ var _ref16 = _asyncToGenerator(function* () {
1623
+ return yield Promise.allSettled(_.map(classes, obj => {
1624
+ return new obj(_extends({}, _this15[_args], {
1625
+ skip_cache: _this15[_skip_cache],
1626
+ _level: _this15[_level] + 1,
1627
+ mapping_dept: _this15[_mapping_dept],
1628
+ mapping_dept_exempt: _this15[_mapping_dept_exempt],
1629
+ _current_path: childPath
1630
+ })).getMulti({
1631
+ skip_hooks: true,
1632
+ ids: ids
1633
+ });
1634
+ }));
1647
1635
  });
1648
- }));
1636
+
1637
+ return function fetchRelated() {
1638
+ return _ref16.apply(this, arguments);
1639
+ };
1640
+ }();
1641
+
1642
+ var returned_all;
1643
+
1644
+ if (p && ids.length > 0) {
1645
+ returned_all = yield p.track(_this15.type + ".map." + source_property, fetchRelated, {
1646
+ ids_count: ids.length
1647
+ });
1648
+ } else {
1649
+ returned_all = yield fetchRelated();
1650
+ }
1649
1651
 
1650
1652
  var ug = _.flatten(_.compact(_.map(returned_all, returned_obj => {
1651
1653
  if (returned_obj.status == "fulfilled") return returned_obj.value;
@@ -1668,6 +1670,11 @@ class SpiceModel {
1668
1670
  var _this16 = this;
1669
1671
 
1670
1672
  return _asyncToGenerator(function* () {
1673
+ var _this16$_ctx;
1674
+
1675
+ // ⚡ Get profiler for proper async context forking
1676
+ var p = (_this16$_ctx = _this16[_ctx]) == null ? void 0 : _this16$_ctx.profiler;
1677
+
1671
1678
  var original_is_array = _.isArray(data);
1672
1679
 
1673
1680
  if (!original_is_array) {
@@ -1698,28 +1705,45 @@ class SpiceModel {
1698
1705
 
1699
1706
  var childPath = _this16[_current_path] ? _this16[_current_path] + "." + source_property : source_property;
1700
1707
 
1701
- var classes = _.compact(_.isArray(Class) ? Class : [Class]);
1708
+ var classes = _.compact(_.isArray(Class) ? Class : [Class]); // ⚡ Wrap in profiler track() to ensure proper async context for child operations
1709
+
1710
+
1711
+ var fetchRelated =
1712
+ /*#__PURE__*/
1713
+ function () {
1714
+ var _ref17 = _asyncToGenerator(function* () {
1715
+ return yield Promise.allSettled(_.map(classes, obj => {
1716
+ return new obj(_extends({}, _this16[_args], {
1717
+ skip_cache: _this16[_skip_cache],
1718
+ _level: _this16[_level] + 1,
1719
+ mapping_dept: _this16[_mapping_dept],
1720
+ mapping_dept_exempt: _this16[_mapping_dept_exempt],
1721
+ _current_path: childPath
1722
+ })).getMulti({
1723
+ skip_hooks: true,
1724
+ ids: ids
1725
+ });
1726
+ }));
1727
+ });
1702
1728
 
1703
- var returned_all = yield Promise.allSettled(_.map(classes, obj => {
1704
- return new obj(_extends({}, _this16[_args], {
1705
- skip_cache: _this16[_skip_cache],
1706
- _level: _this16[_level] + 1,
1707
- mapping_dept: _this16[_mapping_dept],
1708
- mapping_dept_exempt: _this16[_mapping_dept_exempt],
1709
- _current_path: childPath
1710
- })).getMulti({
1711
- skip_hooks: true,
1712
- ids: ids
1729
+ return function fetchRelated() {
1730
+ return _ref17.apply(this, arguments);
1731
+ };
1732
+ }();
1733
+
1734
+ var returned_all;
1735
+
1736
+ if (p && ids.length > 0) {
1737
+ returned_all = yield p.track(_this16.type + ".mapArray." + source_property, fetchRelated, {
1738
+ ids_count: ids.length
1713
1739
  });
1714
- }));
1740
+ } else {
1741
+ returned_all = yield fetchRelated();
1742
+ }
1715
1743
 
1716
1744
  var returned_objects = _.flatten(_.compact(_.map(returned_all, returned_obj => {
1717
1745
  if (returned_obj.status == "fulfilled") return returned_obj.value;
1718
1746
  })));
1719
- /* let returned_objects = await new Class().getMulti({
1720
- ids: ids,
1721
- }); */
1722
-
1723
1747
 
1724
1748
  _.each(data, result => {
1725
1749
  if (_.isString(result[store_property])) {
@@ -1740,21 +1764,14 @@ class SpiceModel {
1740
1764
  })();
1741
1765
  }
1742
1766
 
1743
- addModifier(_ref15) {
1767
+ addModifier(_ref18) {
1744
1768
  var {
1745
1769
  when,
1746
- execute,
1747
- field = null,
1748
- sourceField = null
1749
- } = _ref15;
1770
+ execute
1771
+ } = _ref18;
1750
1772
 
1751
1773
  if (this[_serializers][when]) {
1752
- // Store as object with field info for column-based filtering
1753
- this[_serializers][when]["modifiers"].push({
1754
- execute,
1755
- field,
1756
- sourceField
1757
- });
1774
+ this[_serializers][when]["modifiers"].push(execute);
1758
1775
  }
1759
1776
  }
1760
1777
 
@@ -1783,21 +1800,15 @@ class SpiceModel {
1783
1800
  switch (properties[i].map.type) {
1784
1801
  case _2.MapType.MODEL:
1785
1802
  {
1786
- var destinationField = properties[i].map.destination || i;
1787
-
1788
1803
  switch (properties[i].type) {
1789
1804
  case String:
1790
1805
  case "string":
1791
1806
  {
1792
1807
  _this17.addModifier({
1793
1808
  when: properties[i].map.when || "read",
1794
- field: destinationField,
1795
- // ⚡ Track which field this modifier populates
1796
- sourceField: i,
1797
- // ⚡ Track source property for column filtering
1798
1809
  execute: function () {
1799
1810
  var _execute = _asyncToGenerator(function* (data) {
1800
- return yield _this17.mapToObject(data, _.isString(properties[i].map.reference) ? spice.models[properties[i].map.reference] : properties[i].map.reference, i, destinationField, properties[i]);
1811
+ 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]);
1801
1812
  });
1802
1813
 
1803
1814
  function execute(_x) {
@@ -1816,13 +1827,9 @@ class SpiceModel {
1816
1827
  {
1817
1828
  _this17.addModifier({
1818
1829
  when: properties[i].map.when || "read",
1819
- field: destinationField,
1820
- // ⚡ Track which field this modifier populates
1821
- sourceField: i,
1822
- // ⚡ Track source property for column filtering
1823
1830
  execute: function () {
1824
1831
  var _execute2 = _asyncToGenerator(function* (data) {
1825
- return yield _this17.mapToObjectArray(data, _.isString(properties[i].map.reference) ? spice.models[properties[i].map.reference] : properties[i].map.reference, i, destinationField, properties[i]);
1832
+ 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]);
1826
1833
  });
1827
1834
 
1828
1835
  function execute(_x2) {
@@ -1851,42 +1858,6 @@ class SpiceModel {
1851
1858
  for (var i in properties) {
1852
1859
  _loop(i);
1853
1860
  }
1854
- } // ⚡ Parse columns string into a Set for fast lookup
1855
-
1856
-
1857
- parseRequestedColumns(columns) {
1858
- if (!columns || columns === "") return null; // Handle array of columns (convert to string)
1859
-
1860
- if (Array.isArray(columns)) {
1861
- columns = columns.join(",");
1862
- } // Must be a string to parse
1863
-
1864
-
1865
- if (typeof columns !== "string") return null; // Extract field names from column specifications
1866
- // Handles: "field", "`field`", "table.field", "`table`.`field`", "expr AS alias"
1867
-
1868
- var fields = new Set();
1869
- var columnList = columns.split(",").map(c => c.trim());
1870
-
1871
- for (var col of columnList) {
1872
- // Check for AS alias
1873
- var aliasMatch = col.match(/\s+AS\s+`?([\w]+)`?$/i);
1874
-
1875
- if (aliasMatch) {
1876
- fields.add(aliasMatch[1]);
1877
- continue;
1878
- } // Get the last part after dots (the field name)
1879
-
1880
-
1881
- var parts = col.replace(/`/g, "").split(".");
1882
- var fieldName = parts[parts.length - 1];
1883
-
1884
- if (fieldName && fieldName !== "*") {
1885
- fields.add(fieldName);
1886
- }
1887
- }
1888
-
1889
- return fields.size > 0 ? fields : null;
1890
1861
  }
1891
1862
 
1892
1863
  do_serialize(data, type, old_data, args, path_to_be_removed) {
@@ -1905,7 +1876,7 @@ class SpiceModel {
1905
1876
  var doSerialize =
1906
1877
  /*#__PURE__*/
1907
1878
  function () {
1908
- var _ref16 = _asyncToGenerator(function* () {
1879
+ var _ref19 = _asyncToGenerator(function* () {
1909
1880
  var _this18$_serializers, _this18$_serializers$;
1910
1881
 
1911
1882
  // Early exit if serialization should not run.
@@ -1918,28 +1889,14 @@ class SpiceModel {
1918
1889
  _this18.addExternalModifiers(_this18.type);
1919
1890
 
1920
1891
  _this18[_external_modifier_loaded] = true;
1921
- } // OPTIMIZED: Parse requested columns for selective modifier execution
1922
-
1892
+ } // Cache the modifiers lookup for the specified type.
1923
1893
 
1924
- var requestedColumns = _this18.parseRequestedColumns(args == null ? void 0 : args.columns); // Cache the modifiers lookup for the specified type.
1925
1894
 
1926
-
1927
- var modifiers = ((_this18$_serializers = _this18[_serializers]) == null ? void 0 : (_this18$_serializers$ = _this18$_serializers[type]) == null ? void 0 : _this18$_serializers$.modifiers) || []; // Run modifiers serially
1895
+ var modifiers = ((_this18$_serializers = _this18[_serializers]) == null ? void 0 : (_this18$_serializers$ = _this18$_serializers[type]) == null ? void 0 : _this18$_serializers$.modifiers) || [];
1928
1896
 
1929
1897
  for (var modifier of modifiers) {
1930
- // Skip field-specific modifiers if columns specified and field is not requested
1931
- if (requestedColumns && modifier.field && !requestedColumns.has(modifier.field) && !(modifier.sourceField && requestedColumns.has(modifier.sourceField))) {
1932
- continue;
1933
- }
1934
-
1935
1898
  try {
1936
- // Handle both function modifiers and object modifiers with .execute
1937
- var executeFn = typeof modifier === "function" ? modifier : modifier.execute;
1938
- var result = yield executeFn(data, old_data, _this18[_ctx], _this18.type); // Only assign if modifier returned a value to prevent data corruption
1939
-
1940
- if (result !== undefined) {
1941
- data = result;
1942
- }
1899
+ data = yield modifier(data, old_data, _this18[_ctx], _this18.type);
1943
1900
  } catch (error) {
1944
1901
  console.error("Modifier error in do_serialize:", error.stack);
1945
1902
  }
@@ -1950,42 +1907,33 @@ class SpiceModel {
1950
1907
 
1951
1908
  if (!originalIsArray) {
1952
1909
  data = [data];
1953
- } // OPTIMIZED: Use cached defaults metadata instead of computing every time
1954
-
1955
-
1956
- var {
1957
- staticDefaults,
1958
- dynamicDefaults,
1959
- hasDefaults
1960
- } = _this18.getDefaultsMetadata(type);
1961
-
1962
- if (hasDefaults) {
1963
- data = data.map(item => {
1964
- // Apply static defaults first (fast - no function calls)
1965
- var result = _.defaults({}, item, staticDefaults); // Only compute dynamic defaults if there are any
1966
-
1967
-
1968
- for (var {
1969
- key,
1970
- fn
1971
- } of dynamicDefaults) {
1972
- if (result[key] === undefined) {
1973
- result[key] = fn({
1974
- old_data: data,
1975
- new_data: old_data
1976
- });
1977
- }
1978
- }
1910
+ } // Compute the defaults from properties using reduce.
1979
1911
 
1980
- return result;
1981
- });
1982
- } // If type is "read", clean the data by omitting certain props.
1983
1912
 
1913
+ var defaults = Object.keys(_this18.props).reduce((acc, key) => {
1914
+ var _this18$props$key, _this18$props$key$def;
1915
+
1916
+ var def = (_this18$props$key = _this18.props[key]) == null ? void 0 : (_this18$props$key$def = _this18$props$key.defaults) == null ? void 0 : _this18$props$key$def[type];
1917
+
1918
+ if (def !== undefined) {
1919
+ acc[key] = _.isFunction(def) ? def({
1920
+ old_data: data,
1921
+ new_data: old_data
1922
+ }) : def;
1923
+ }
1924
+
1925
+ return acc;
1926
+ }, {}); // Merge defaults into each object.
1927
+
1928
+ data = data.map(item => _.defaults(item, defaults)); // If type is "read", clean the data by omitting certain props.
1984
1929
 
1985
1930
  if (type === "read") {
1986
- // OPTIMIZED: Use cached hidden props instead of computing every time
1987
- var hiddenProps = _this18.getHiddenProps(); // Combine default props to remove.
1931
+ // Collect hidden properties from schema.
1932
+ var hiddenProps = Object.keys(_this18.props).filter(key => {
1933
+ var _this18$props$key2;
1988
1934
 
1935
+ return (_this18$props$key2 = _this18.props[key]) == null ? void 0 : _this18$props$key2.hide;
1936
+ }); // Combine default props to remove.
1989
1937
 
1990
1938
  var propsToClean = ["deleted", "type", ...path_to_be_removed, ...hiddenProps];
1991
1939
  data = data.map(item => _.omit(item, propsToClean));
@@ -1996,7 +1944,7 @@ class SpiceModel {
1996
1944
  });
1997
1945
 
1998
1946
  return function doSerialize() {
1999
- return _ref16.apply(this, arguments);
1947
+ return _ref19.apply(this, arguments);
2000
1948
  };
2001
1949
  }();
2002
1950
 
@@ -2017,6 +1965,4 @@ class SpiceModel {
2017
1965
 
2018
1966
  }
2019
1967
 
2020
- exports.default = SpiceModel;
2021
- SpiceModel._defaultsCache = {};
2022
- SpiceModel._hiddenPropsCache = {};
1968
+ exports.default = SpiceModel;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spice-js",
3
- "version": "2.7.2",
3
+ "version": "2.7.4",
4
4
  "description": "spice",
5
5
  "main": "build/index.js",
6
6
  "repository": {
@@ -44,10 +44,6 @@ if (!Promise.allSettled) {
44
44
  );
45
45
  }
46
46
  export default class SpiceModel {
47
- // ⚡ Static caches for performance optimization
48
- static _defaultsCache = {};
49
- static _hiddenPropsCache = {};
50
-
51
47
  constructor(args = {}) {
52
48
  try {
53
49
  var dbtype =
@@ -606,7 +602,10 @@ export default class SpiceModel {
606
602
  }
607
603
 
608
604
  async getMulti(args) {
609
- try {
605
+ // ⚡ Profiling: use track() for proper async context forking
606
+ const p = this[_ctx]?.profiler;
607
+
608
+ const doGetMulti = async () => {
610
609
  if (!args) {
611
610
  args = {};
612
611
  }
@@ -653,6 +652,15 @@ export default class SpiceModel {
653
652
  }
654
653
 
655
654
  return results;
655
+ };
656
+
657
+ try {
658
+ if (p) {
659
+ return await p.track(`${this.type}.getMulti`, doGetMulti, {
660
+ ids_count: args?.ids?.length || 0,
661
+ });
662
+ }
663
+ return await doGetMulti();
656
664
  } catch (e) {
657
665
  console.warn(e.stack);
658
666
  throw e;
@@ -1379,47 +1387,6 @@ export default class SpiceModel {
1379
1387
  return true;
1380
1388
  }
1381
1389
 
1382
- // ⚡ OPTIMIZED: Cache defaults metadata per model type
1383
- getDefaultsMetadata(type) {
1384
- const cacheKey = `${this.type}::${type}`;
1385
-
1386
- if (!SpiceModel._defaultsCache[cacheKey]) {
1387
- const staticDefaults = {};
1388
- const dynamicDefaults = [];
1389
-
1390
- // Pre-compute once per model type
1391
- for (const key in this.props) {
1392
- const def = this.props[key]?.defaults?.[type];
1393
- if (def !== undefined) {
1394
- if (_.isFunction(def)) {
1395
- dynamicDefaults.push({ key, fn: def });
1396
- } else {
1397
- staticDefaults[key] = def;
1398
- }
1399
- }
1400
- }
1401
-
1402
- SpiceModel._defaultsCache[cacheKey] = {
1403
- staticDefaults,
1404
- dynamicDefaults,
1405
- hasDefaults:
1406
- Object.keys(staticDefaults).length > 0 || dynamicDefaults.length > 0,
1407
- };
1408
- }
1409
-
1410
- return SpiceModel._defaultsCache[cacheKey];
1411
- }
1412
-
1413
- // ⚡ OPTIMIZED: Cache hidden props per model type
1414
- getHiddenProps() {
1415
- if (!SpiceModel._hiddenPropsCache[this.type]) {
1416
- SpiceModel._hiddenPropsCache[this.type] = Object.keys(this.props).filter(
1417
- (key) => this.props[key]?.hide
1418
- );
1419
- }
1420
- return SpiceModel._hiddenPropsCache[this.type];
1421
- }
1422
-
1423
1390
  // Check if a field is exempt from mapping depth limits
1424
1391
  // Supports deep references like "group.permissions"
1425
1392
  isFieldExempt(source_property) {
@@ -1445,6 +1412,9 @@ export default class SpiceModel {
1445
1412
  }
1446
1413
 
1447
1414
  async mapToObject(data, Class, source_property, store_property, property) {
1415
+ // ⚡ Get profiler for proper async context forking
1416
+ const p = this[_ctx]?.profiler;
1417
+
1448
1418
  let original_is_array = _.isArray(data);
1449
1419
  if (!original_is_array) {
1450
1420
  data = Array.of(data);
@@ -1469,21 +1439,36 @@ export default class SpiceModel {
1469
1439
  `${this[_current_path]}.${source_property}`
1470
1440
  : source_property;
1471
1441
 
1472
- var returned_all = await Promise.allSettled(
1473
- _.map(classes, (obj) => {
1474
- return new obj({
1475
- ...this[_args],
1476
- skip_cache: this[_skip_cache],
1477
- _level: this[_level] + 1,
1478
- mapping_dept: this[_mapping_dept],
1479
- mapping_dept_exempt: this[_mapping_dept_exempt],
1480
- _current_path: childPath,
1481
- }).getMulti({
1482
- skip_hooks: true,
1483
- ids: ids,
1484
- });
1485
- })
1486
- );
1442
+ // Wrap in profiler track() to ensure proper async context for child operations
1443
+ const fetchRelated = async () => {
1444
+ return await Promise.allSettled(
1445
+ _.map(classes, (obj) => {
1446
+ return new obj({
1447
+ ...this[_args],
1448
+ skip_cache: this[_skip_cache],
1449
+ _level: this[_level] + 1,
1450
+ mapping_dept: this[_mapping_dept],
1451
+ mapping_dept_exempt: this[_mapping_dept_exempt],
1452
+ _current_path: childPath,
1453
+ }).getMulti({
1454
+ skip_hooks: true,
1455
+ ids: ids,
1456
+ });
1457
+ })
1458
+ );
1459
+ };
1460
+
1461
+ var returned_all;
1462
+ if (p && ids.length > 0) {
1463
+ returned_all = await p.track(
1464
+ `${this.type}.map.${source_property}`,
1465
+ fetchRelated,
1466
+ { ids_count: ids.length }
1467
+ );
1468
+ } else {
1469
+ returned_all = await fetchRelated();
1470
+ }
1471
+
1487
1472
  let ug = _.flatten(
1488
1473
  _.compact(
1489
1474
  _.map(returned_all, (returned_obj) => {
@@ -1511,6 +1496,9 @@ export default class SpiceModel {
1511
1496
  store_property,
1512
1497
  property
1513
1498
  ) {
1499
+ // ⚡ Get profiler for proper async context forking
1500
+ const p = this[_ctx]?.profiler;
1501
+
1514
1502
  let original_is_array = _.isArray(data);
1515
1503
  if (!original_is_array) {
1516
1504
  data = Array.of(data);
@@ -1540,21 +1528,36 @@ export default class SpiceModel {
1540
1528
  : source_property;
1541
1529
 
1542
1530
  let classes = _.compact(_.isArray(Class) ? Class : [Class]);
1543
- var returned_all = await Promise.allSettled(
1544
- _.map(classes, (obj) => {
1545
- return new obj({
1546
- ...this[_args],
1547
- skip_cache: this[_skip_cache],
1548
- _level: this[_level] + 1,
1549
- mapping_dept: this[_mapping_dept],
1550
- mapping_dept_exempt: this[_mapping_dept_exempt],
1551
- _current_path: childPath,
1552
- }).getMulti({
1553
- skip_hooks: true,
1554
- ids: ids,
1555
- });
1556
- })
1557
- );
1531
+
1532
+ // ⚡ Wrap in profiler track() to ensure proper async context for child operations
1533
+ const fetchRelated = async () => {
1534
+ return await Promise.allSettled(
1535
+ _.map(classes, (obj) => {
1536
+ return new obj({
1537
+ ...this[_args],
1538
+ skip_cache: this[_skip_cache],
1539
+ _level: this[_level] + 1,
1540
+ mapping_dept: this[_mapping_dept],
1541
+ mapping_dept_exempt: this[_mapping_dept_exempt],
1542
+ _current_path: childPath,
1543
+ }).getMulti({
1544
+ skip_hooks: true,
1545
+ ids: ids,
1546
+ });
1547
+ })
1548
+ );
1549
+ };
1550
+
1551
+ var returned_all;
1552
+ if (p && ids.length > 0) {
1553
+ returned_all = await p.track(
1554
+ `${this.type}.mapArray.${source_property}`,
1555
+ fetchRelated,
1556
+ { ids_count: ids.length }
1557
+ );
1558
+ } else {
1559
+ returned_all = await fetchRelated();
1560
+ }
1558
1561
 
1559
1562
  var returned_objects = _.flatten(
1560
1563
  _.compact(
@@ -1564,9 +1567,6 @@ export default class SpiceModel {
1564
1567
  )
1565
1568
  );
1566
1569
 
1567
- /* let returned_objects = await new Class().getMulti({
1568
- ids: ids,
1569
- }); */
1570
1570
  _.each(data, (result) => {
1571
1571
  if (_.isString(result[store_property])) {
1572
1572
  result[store_property] = [result[store_property]];
@@ -1589,14 +1589,9 @@ export default class SpiceModel {
1589
1589
  return original_is_array ? data : data[0];
1590
1590
  }
1591
1591
 
1592
- addModifier({ when, execute, field = null, sourceField = null }) {
1592
+ addModifier({ when, execute }) {
1593
1593
  if (this[_serializers][when]) {
1594
- // Store as object with field info for column-based filtering
1595
- this[_serializers][when]["modifiers"].push({
1596
- execute,
1597
- field,
1598
- sourceField,
1599
- });
1594
+ this[_serializers][when]["modifiers"].push(execute);
1600
1595
  }
1601
1596
  }
1602
1597
 
@@ -1617,14 +1612,11 @@ export default class SpiceModel {
1617
1612
  if (properties[i].map) {
1618
1613
  switch (properties[i].map.type) {
1619
1614
  case MapType.MODEL: {
1620
- const destinationField = properties[i].map.destination || i;
1621
1615
  switch (properties[i].type) {
1622
1616
  case String:
1623
1617
  case "string": {
1624
1618
  this.addModifier({
1625
1619
  when: properties[i].map.when || "read",
1626
- field: destinationField, // ⚡ Track which field this modifier populates
1627
- sourceField: i, // ⚡ Track source property for column filtering
1628
1620
  execute: async (data) => {
1629
1621
  return await this.mapToObject(
1630
1622
  data,
@@ -1632,7 +1624,7 @@ export default class SpiceModel {
1632
1624
  spice.models[properties[i].map.reference]
1633
1625
  : properties[i].map.reference,
1634
1626
  i,
1635
- destinationField,
1627
+ properties[i].map.destination || i,
1636
1628
  properties[i]
1637
1629
  );
1638
1630
  },
@@ -1643,8 +1635,6 @@ export default class SpiceModel {
1643
1635
  case "array": {
1644
1636
  this.addModifier({
1645
1637
  when: properties[i].map.when || "read",
1646
- field: destinationField, // ⚡ Track which field this modifier populates
1647
- sourceField: i, // ⚡ Track source property for column filtering
1648
1638
  execute: async (data) => {
1649
1639
  return await this.mapToObjectArray(
1650
1640
  data,
@@ -1652,7 +1642,7 @@ export default class SpiceModel {
1652
1642
  spice.models[properties[i].map.reference]
1653
1643
  : properties[i].map.reference,
1654
1644
  i,
1655
- destinationField,
1645
+ properties[i].map.destination || i,
1656
1646
  properties[i]
1657
1647
  );
1658
1648
  },
@@ -1670,41 +1660,6 @@ export default class SpiceModel {
1670
1660
  }
1671
1661
  }
1672
1662
 
1673
- // ⚡ Parse columns string into a Set for fast lookup
1674
- parseRequestedColumns(columns) {
1675
- if (!columns || columns === "") return null;
1676
-
1677
- // Handle array of columns (convert to string)
1678
- if (Array.isArray(columns)) {
1679
- columns = columns.join(",");
1680
- }
1681
-
1682
- // Must be a string to parse
1683
- if (typeof columns !== "string") return null;
1684
- // Extract field names from column specifications
1685
- // Handles: "field", "`field`", "table.field", "`table`.`field`", "expr AS alias"
1686
- const fields = new Set();
1687
- const columnList = columns.split(",").map((c) => c.trim());
1688
-
1689
- for (const col of columnList) {
1690
- // Check for AS alias
1691
- const aliasMatch = col.match(/\s+AS\s+`?([\w]+)`?$/i);
1692
- if (aliasMatch) {
1693
- fields.add(aliasMatch[1]);
1694
- continue;
1695
- }
1696
-
1697
- // Get the last part after dots (the field name)
1698
- const parts = col.replace(/`/g, "").split(".");
1699
- const fieldName = parts[parts.length - 1];
1700
- if (fieldName && fieldName !== "*") {
1701
- fields.add(fieldName);
1702
- }
1703
- }
1704
-
1705
- return fields.size > 0 ? fields : null;
1706
- }
1707
-
1708
1663
  async do_serialize(data, type, old_data, args, path_to_be_removed = []) {
1709
1664
  // Profiling: use track() for proper async context forking
1710
1665
  const p = this[_ctx]?.profiler;
@@ -1721,33 +1676,11 @@ export default class SpiceModel {
1721
1676
  this[_external_modifier_loaded] = true;
1722
1677
  }
1723
1678
 
1724
- // ⚡ OPTIMIZED: Parse requested columns for selective modifier execution
1725
- const requestedColumns = this.parseRequestedColumns(args?.columns);
1726
-
1727
1679
  // Cache the modifiers lookup for the specified type.
1728
1680
  const modifiers = this[_serializers]?.[type]?.modifiers || [];
1729
-
1730
- // Run modifiers serially
1731
1681
  for (const modifier of modifiers) {
1732
- // Skip field-specific modifiers if columns specified and field is not requested
1733
- if (
1734
- requestedColumns &&
1735
- modifier.field &&
1736
- !requestedColumns.has(modifier.field) &&
1737
- !(modifier.sourceField && requestedColumns.has(modifier.sourceField))
1738
- ) {
1739
- continue;
1740
- }
1741
-
1742
1682
  try {
1743
- // Handle both function modifiers and object modifiers with .execute
1744
- const executeFn =
1745
- typeof modifier === "function" ? modifier : modifier.execute;
1746
- const result = await executeFn(data, old_data, this[_ctx], this.type);
1747
- // Only assign if modifier returned a value to prevent data corruption
1748
- if (result !== undefined) {
1749
- data = result;
1750
- }
1683
+ data = await modifier(data, old_data, this[_ctx], this.type);
1751
1684
  } catch (error) {
1752
1685
  console.error("Modifier error in do_serialize:", error.stack);
1753
1686
  }
@@ -1759,30 +1692,27 @@ export default class SpiceModel {
1759
1692
  data = [data];
1760
1693
  }
1761
1694
 
1762
- // OPTIMIZED: Use cached defaults metadata instead of computing every time
1763
- const { staticDefaults, dynamicDefaults, hasDefaults } =
1764
- this.getDefaultsMetadata(type);
1765
-
1766
- if (hasDefaults) {
1767
- data = data.map((item) => {
1768
- // Apply static defaults first (fast - no function calls)
1769
- const result = _.defaults({}, item, staticDefaults);
1770
-
1771
- // Only compute dynamic defaults if there are any
1772
- for (const { key, fn } of dynamicDefaults) {
1773
- if (result[key] === undefined) {
1774
- result[key] = fn({ old_data: data, new_data: old_data });
1775
- }
1776
- }
1695
+ // Compute the defaults from properties using reduce.
1696
+ const defaults = Object.keys(this.props).reduce((acc, key) => {
1697
+ const def = this.props[key]?.defaults?.[type];
1698
+ if (def !== undefined) {
1699
+ acc[key] =
1700
+ _.isFunction(def) ?
1701
+ def({ old_data: data, new_data: old_data })
1702
+ : def;
1703
+ }
1704
+ return acc;
1705
+ }, {});
1777
1706
 
1778
- return result;
1779
- });
1780
- }
1707
+ // Merge defaults into each object.
1708
+ data = data.map((item) => _.defaults(item, defaults));
1781
1709
 
1782
1710
  // If type is "read", clean the data by omitting certain props.
1783
1711
  if (type === "read") {
1784
- // OPTIMIZED: Use cached hidden props instead of computing every time
1785
- const hiddenProps = this.getHiddenProps();
1712
+ // Collect hidden properties from schema.
1713
+ const hiddenProps = Object.keys(this.props).filter(
1714
+ (key) => this.props[key]?.hide
1715
+ );
1786
1716
  // Combine default props to remove.
1787
1717
  const propsToClean = [
1788
1718
  "deleted",