spice-js 2.6.73 → 2.6.75

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.
@@ -584,9 +584,10 @@ class SpiceModel {
584
584
  if (_.isString(args.id)) {
585
585
  if (args.skip_hooks !== true) {
586
586
  yield _this4.run_hook(args, "get", "before");
587
- }
587
+ } // ⚡ Include columns in cache key if specified
588
+
588
589
 
589
- var key = "get::" + _this4.type + "::" + args.id;
590
+ var key = "get::" + _this4.type + "::" + args.id + (args.columns ? "::" + args.columns : "");
590
591
  var results = {};
591
592
 
592
593
  if (_this4.shouldUseCache(_this4.type)) {
@@ -640,7 +641,13 @@ class SpiceModel {
640
641
 
641
642
  if (results.deleted === undefined || results.deleted === false) {
642
643
  if (args.skip_read_serialize !== true && args.skip_serialize !== true) {
644
+ // ⚡ Pass columns to do_serialize so it can skip irrelevant modifiers
643
645
  results = yield _this4.do_serialize(results, "read", {}, args, (yield _this4.propsToBeRemoved(results)));
646
+ } // ⚡ OPTIMIZED: Filter results by columns if specified
647
+
648
+
649
+ if (args.columns) {
650
+ results = _this4.filterResultsByColumns([results], args.columns)[0];
644
651
  }
645
652
 
646
653
  if (args.skip_hooks !== true) {
@@ -1695,11 +1702,16 @@ class SpiceModel {
1695
1702
  addModifier(_ref15) {
1696
1703
  var {
1697
1704
  when,
1698
- execute
1705
+ execute,
1706
+ field = null
1699
1707
  } = _ref15;
1700
1708
 
1701
1709
  if (this[_serializers][when]) {
1702
- this[_serializers][when]["modifiers"].push(execute);
1710
+ // Store as object with field info for column-based filtering
1711
+ this[_serializers][when]["modifiers"].push({
1712
+ execute,
1713
+ field
1714
+ });
1703
1715
  }
1704
1716
  }
1705
1717
 
@@ -1728,15 +1740,19 @@ class SpiceModel {
1728
1740
  switch (properties[i].map.type) {
1729
1741
  case _2.MapType.MODEL:
1730
1742
  {
1743
+ var destinationField = properties[i].map.destination || i;
1744
+
1731
1745
  switch (properties[i].type) {
1732
1746
  case String:
1733
1747
  case "string":
1734
1748
  {
1735
1749
  _this17.addModifier({
1736
1750
  when: properties[i].map.when || "read",
1751
+ field: destinationField,
1752
+ // ⚡ Track which field this modifier populates
1737
1753
  execute: function () {
1738
1754
  var _execute = _asyncToGenerator(function* (data) {
1739
- 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]);
1755
+ return yield _this17.mapToObject(data, _.isString(properties[i].map.reference) ? spice.models[properties[i].map.reference] : properties[i].map.reference, i, destinationField, properties[i]);
1740
1756
  });
1741
1757
 
1742
1758
  function execute(_x) {
@@ -1755,9 +1771,11 @@ class SpiceModel {
1755
1771
  {
1756
1772
  _this17.addModifier({
1757
1773
  when: properties[i].map.when || "read",
1774
+ field: destinationField,
1775
+ // ⚡ Track which field this modifier populates
1758
1776
  execute: function () {
1759
1777
  var _execute2 = _asyncToGenerator(function* (data) {
1760
- 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]);
1778
+ return yield _this17.mapToObjectArray(data, _.isString(properties[i].map.reference) ? spice.models[properties[i].map.reference] : properties[i].map.reference, i, destinationField, properties[i]);
1761
1779
  });
1762
1780
 
1763
1781
  function execute(_x2) {
@@ -1786,6 +1804,35 @@ class SpiceModel {
1786
1804
  for (var i in properties) {
1787
1805
  _loop(i);
1788
1806
  }
1807
+ } // ⚡ Parse columns string into a Set for fast lookup
1808
+
1809
+
1810
+ parseRequestedColumns(columns) {
1811
+ if (!columns || columns === "") return null; // Extract field names from column specifications
1812
+ // Handles: "field", "`field`", "table.field", "`table`.`field`", "expr AS alias"
1813
+
1814
+ var fields = new Set();
1815
+ var columnList = columns.split(",").map(c => c.trim());
1816
+
1817
+ for (var col of columnList) {
1818
+ // Check for AS alias
1819
+ var aliasMatch = col.match(/\s+AS\s+`?([\w]+)`?$/i);
1820
+
1821
+ if (aliasMatch) {
1822
+ fields.add(aliasMatch[1]);
1823
+ continue;
1824
+ } // Get the last part after dots (the field name)
1825
+
1826
+
1827
+ var parts = col.replace(/`/g, "").split(".");
1828
+ var fieldName = parts[parts.length - 1];
1829
+
1830
+ if (fieldName && fieldName !== "*") {
1831
+ fields.add(fieldName);
1832
+ }
1833
+ }
1834
+
1835
+ return fields.size > 0 ? fields : null;
1789
1836
  }
1790
1837
 
1791
1838
  do_serialize(data, type, old_data, args, path_to_be_removed) {
@@ -1817,14 +1864,24 @@ class SpiceModel {
1817
1864
  _this18.addExternalModifiers(_this18.type);
1818
1865
 
1819
1866
  _this18[_external_modifier_loaded] = true;
1820
- } // Cache the modifiers lookup for the specified type.
1867
+ } // OPTIMIZED: Parse requested columns for selective modifier execution
1868
+
1869
+
1870
+ var requestedColumns = _this18.parseRequestedColumns(args == null ? void 0 : args.columns); // Cache the modifiers lookup for the specified type.
1821
1871
 
1822
1872
 
1823
1873
  var modifiers = ((_this18$_serializers = _this18[_serializers]) == null ? void 0 : (_this18$_serializers$ = _this18$_serializers[type]) == null ? void 0 : _this18$_serializers$.modifiers) || [];
1824
1874
 
1825
1875
  for (var modifier of modifiers) {
1826
1876
  try {
1827
- data = yield modifier(data, old_data, _this18[_ctx], _this18.type);
1877
+ // OPTIMIZED: Skip field-specific modifiers if columns specified and field not requested
1878
+ if (requestedColumns && modifier.field && !requestedColumns.has(modifier.field)) {
1879
+ continue; // Skip this modifier - field not in requested columns
1880
+ } // Execute modifier (supports both old function format and new object format)
1881
+
1882
+
1883
+ var executeFn = typeof modifier === "function" ? modifier : modifier.execute;
1884
+ data = yield executeFn(data, old_data, _this18[_ctx], _this18.type);
1828
1885
  } catch (error) {
1829
1886
  console.error("Modifier error in do_serialize:", error.stack);
1830
1887
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spice-js",
3
- "version": "2.6.73",
3
+ "version": "2.6.75",
4
4
  "description": "spice",
5
5
  "main": "build/index.js",
6
6
  "repository": {
@@ -20,7 +20,8 @@ var SDate = require("sonover-date"),
20
20
  _skip_cache = Symbol(),
21
21
  _serializers = Symbol(),
22
22
  _level = Symbol(),
23
- _mapping_dept = Symbol();
23
+ _mapping_dept = Symbol(),
24
+ _mapping_dept_exempt = Symbol();
24
25
 
25
26
  //const _type = Symbol("type");
26
27
 
@@ -52,6 +53,7 @@ export default class SpiceModel {
52
53
  spice.config.database.connections[args.connection].type || "couchbase";
53
54
  let Database = require(`spice-${dbtype}`);
54
55
  this[_mapping_dept] = args?.args?.mapping_dept || 100;
56
+ this[_mapping_dept_exempt] = args?.args?.mapping_dept_exempt || [];
55
57
  this.type = "";
56
58
  this.collection = args.connection;
57
59
  this[_args] = args.args;
@@ -491,6 +493,8 @@ export default class SpiceModel {
491
493
 
492
494
  const doGet = async () => {
493
495
  if (args.mapping_dept) this[_mapping_dept] = args.mapping_dept;
496
+ if (args.mapping_dept_exempt)
497
+ this[_mapping_dept_exempt] = args.mapping_dept_exempt;
494
498
 
495
499
  if (!args) {
496
500
  args = {};
@@ -499,7 +503,8 @@ export default class SpiceModel {
499
503
  if (args.skip_hooks !== true) {
500
504
  await this.run_hook(args, "get", "before");
501
505
  }
502
- let key = `get::${this.type}::${args.id}`;
506
+ // ⚡ Include columns in cache key if specified
507
+ let key = `get::${this.type}::${args.id}${args.columns ? `::${args.columns}` : ""}`;
503
508
  let results = {};
504
509
 
505
510
  if (this.shouldUseCache(this.type)) {
@@ -553,6 +558,7 @@ export default class SpiceModel {
553
558
  args.skip_read_serialize !== true &&
554
559
  args.skip_serialize !== true
555
560
  ) {
561
+ // ⚡ Pass columns to do_serialize so it can skip irrelevant modifiers
556
562
  results = await this.do_serialize(
557
563
  results,
558
564
  "read",
@@ -561,6 +567,12 @@ export default class SpiceModel {
561
567
  await this.propsToBeRemoved(results)
562
568
  );
563
569
  }
570
+
571
+ // ⚡ OPTIMIZED: Filter results by columns if specified
572
+ if (args.columns) {
573
+ results = this.filterResultsByColumns([results], args.columns)[0];
574
+ }
575
+
564
576
  if (args.skip_hooks !== true) {
565
577
  await this.run_hook(results, "get", "after");
566
578
  }
@@ -1136,6 +1148,8 @@ export default class SpiceModel {
1136
1148
 
1137
1149
  const doList = async () => {
1138
1150
  if (args.mapping_dept) this[_mapping_dept] = args.mapping_dept;
1151
+ if (args.mapping_dept_exempt)
1152
+ this[_mapping_dept_exempt] = args.mapping_dept_exempt;
1139
1153
 
1140
1154
  // Find alias tokens from query/columns/sort
1141
1155
  const nestings = [
@@ -1409,9 +1423,8 @@ export default class SpiceModel {
1409
1423
  if (!original_is_array) {
1410
1424
  data = Array.of(data);
1411
1425
  }
1412
- this[_mapping_dept];
1413
-
1414
- if (this[_level] + 1 < this[_mapping_dept]) {
1426
+ const isExempt = this[_mapping_dept_exempt].includes(source_property);
1427
+ if (isExempt || this[_level] + 1 < this[_mapping_dept]) {
1415
1428
  let classes = _.compact(_.isArray(Class) ? Class : [Class]);
1416
1429
 
1417
1430
  let ids = [];
@@ -1431,6 +1444,7 @@ export default class SpiceModel {
1431
1444
  skip_cache: this[_skip_cache],
1432
1445
  _level: this[_level] + 1,
1433
1446
  mapping_dept: this[_mapping_dept],
1447
+ mapping_dept_exempt: this[_mapping_dept_exempt],
1434
1448
  }).getMulti({
1435
1449
  skip_hooks: true,
1436
1450
  ids: ids,
@@ -1468,7 +1482,8 @@ export default class SpiceModel {
1468
1482
  if (!original_is_array) {
1469
1483
  data = Array.of(data);
1470
1484
  }
1471
- if (this[_level] + 1 < this[_mapping_dept]) {
1485
+ const isExempt = this[_mapping_dept_exempt].includes(source_property);
1486
+ if (isExempt || this[_level] + 1 < this[_mapping_dept]) {
1472
1487
  let ids = [];
1473
1488
  _.each(data, (result) => {
1474
1489
  let value = [];
@@ -1493,6 +1508,7 @@ export default class SpiceModel {
1493
1508
  skip_cache: this[_skip_cache],
1494
1509
  _level: this[_level] + 1,
1495
1510
  mapping_dept: this[_mapping_dept],
1511
+ mapping_dept_exempt: this[_mapping_dept_exempt],
1496
1512
  }).getMulti({
1497
1513
  skip_hooks: true,
1498
1514
  ids: ids,
@@ -1533,9 +1549,10 @@ export default class SpiceModel {
1533
1549
  return original_is_array ? data : data[0];
1534
1550
  }
1535
1551
 
1536
- addModifier({ when, execute }) {
1552
+ addModifier({ when, execute, field = null }) {
1537
1553
  if (this[_serializers][when]) {
1538
- this[_serializers][when]["modifiers"].push(execute);
1554
+ // Store as object with field info for column-based filtering
1555
+ this[_serializers][when]["modifiers"].push({ execute, field });
1539
1556
  }
1540
1557
  }
1541
1558
 
@@ -1556,11 +1573,13 @@ export default class SpiceModel {
1556
1573
  if (properties[i].map) {
1557
1574
  switch (properties[i].map.type) {
1558
1575
  case MapType.MODEL: {
1576
+ const destinationField = properties[i].map.destination || i;
1559
1577
  switch (properties[i].type) {
1560
1578
  case String:
1561
1579
  case "string": {
1562
1580
  this.addModifier({
1563
1581
  when: properties[i].map.when || "read",
1582
+ field: destinationField, // ⚡ Track which field this modifier populates
1564
1583
  execute: async (data) => {
1565
1584
  return await this.mapToObject(
1566
1585
  data,
@@ -1568,7 +1587,7 @@ export default class SpiceModel {
1568
1587
  spice.models[properties[i].map.reference]
1569
1588
  : properties[i].map.reference,
1570
1589
  i,
1571
- properties[i].map.destination || i,
1590
+ destinationField,
1572
1591
  properties[i]
1573
1592
  );
1574
1593
  },
@@ -1579,6 +1598,7 @@ export default class SpiceModel {
1579
1598
  case "array": {
1580
1599
  this.addModifier({
1581
1600
  when: properties[i].map.when || "read",
1601
+ field: destinationField, // ⚡ Track which field this modifier populates
1582
1602
  execute: async (data) => {
1583
1603
  return await this.mapToObjectArray(
1584
1604
  data,
@@ -1586,7 +1606,7 @@ export default class SpiceModel {
1586
1606
  spice.models[properties[i].map.reference]
1587
1607
  : properties[i].map.reference,
1588
1608
  i,
1589
- properties[i].map.destination || i,
1609
+ destinationField,
1590
1610
  properties[i]
1591
1611
  );
1592
1612
  },
@@ -1604,6 +1624,34 @@ export default class SpiceModel {
1604
1624
  }
1605
1625
  }
1606
1626
 
1627
+ // ⚡ Parse columns string into a Set for fast lookup
1628
+ parseRequestedColumns(columns) {
1629
+ if (!columns || columns === "") return null;
1630
+
1631
+ // Extract field names from column specifications
1632
+ // Handles: "field", "`field`", "table.field", "`table`.`field`", "expr AS alias"
1633
+ const fields = new Set();
1634
+ const columnList = columns.split(",").map((c) => c.trim());
1635
+
1636
+ for (const col of columnList) {
1637
+ // Check for AS alias
1638
+ const aliasMatch = col.match(/\s+AS\s+`?([\w]+)`?$/i);
1639
+ if (aliasMatch) {
1640
+ fields.add(aliasMatch[1]);
1641
+ continue;
1642
+ }
1643
+
1644
+ // Get the last part after dots (the field name)
1645
+ const parts = col.replace(/`/g, "").split(".");
1646
+ const fieldName = parts[parts.length - 1];
1647
+ if (fieldName && fieldName !== "*") {
1648
+ fields.add(fieldName);
1649
+ }
1650
+ }
1651
+
1652
+ return fields.size > 0 ? fields : null;
1653
+ }
1654
+
1607
1655
  async do_serialize(data, type, old_data, args, path_to_be_removed = []) {
1608
1656
  // Profiling: use track() for proper async context forking
1609
1657
  const p = this[_ctx]?.profiler;
@@ -1620,11 +1668,26 @@ export default class SpiceModel {
1620
1668
  this[_external_modifier_loaded] = true;
1621
1669
  }
1622
1670
 
1671
+ // ⚡ OPTIMIZED: Parse requested columns for selective modifier execution
1672
+ const requestedColumns = this.parseRequestedColumns(args?.columns);
1673
+
1623
1674
  // Cache the modifiers lookup for the specified type.
1624
1675
  const modifiers = this[_serializers]?.[type]?.modifiers || [];
1625
1676
  for (const modifier of modifiers) {
1626
1677
  try {
1627
- data = await modifier(data, old_data, this[_ctx], this.type);
1678
+ // OPTIMIZED: Skip field-specific modifiers if columns specified and field not requested
1679
+ if (
1680
+ requestedColumns &&
1681
+ modifier.field &&
1682
+ !requestedColumns.has(modifier.field)
1683
+ ) {
1684
+ continue; // Skip this modifier - field not in requested columns
1685
+ }
1686
+
1687
+ // Execute modifier (supports both old function format and new object format)
1688
+ const executeFn =
1689
+ typeof modifier === "function" ? modifier : modifier.execute;
1690
+ data = await executeFn(data, old_data, this[_ctx], this.type);
1628
1691
  } catch (error) {
1629
1692
  console.error("Modifier error in do_serialize:", error.stack);
1630
1693
  }