spice-js 2.7.3 → 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 = {};
@@ -1559,54 +1558,6 @@ class SpiceModel {
1559
1558
  }
1560
1559
 
1561
1560
  return true;
1562
- } // ⚡ OPTIMIZED: Cache defaults metadata per model type
1563
-
1564
-
1565
- getDefaultsMetadata(type) {
1566
- var cacheKey = this.type + "::" + type;
1567
-
1568
- if (!SpiceModel._defaultsCache[cacheKey]) {
1569
- var staticDefaults = {};
1570
- var dynamicDefaults = []; // Pre-compute once per model type
1571
-
1572
- for (var key in this.props) {
1573
- var _this$props$key, _this$props$key$defau;
1574
-
1575
- 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];
1576
-
1577
- if (def !== undefined) {
1578
- if (_.isFunction(def)) {
1579
- dynamicDefaults.push({
1580
- key,
1581
- fn: def
1582
- });
1583
- } else {
1584
- staticDefaults[key] = def;
1585
- }
1586
- }
1587
- }
1588
-
1589
- SpiceModel._defaultsCache[cacheKey] = {
1590
- staticDefaults,
1591
- dynamicDefaults,
1592
- hasDefaults: Object.keys(staticDefaults).length > 0 || dynamicDefaults.length > 0
1593
- };
1594
- }
1595
-
1596
- return SpiceModel._defaultsCache[cacheKey];
1597
- } // ⚡ OPTIMIZED: Cache hidden props per model type
1598
-
1599
-
1600
- getHiddenProps() {
1601
- if (!SpiceModel._hiddenPropsCache[this.type]) {
1602
- SpiceModel._hiddenPropsCache[this.type] = Object.keys(this.props).filter(key => {
1603
- var _this$props$key2;
1604
-
1605
- return (_this$props$key2 = this.props[key]) == null ? void 0 : _this$props$key2.hide;
1606
- });
1607
- }
1608
-
1609
- return SpiceModel._hiddenPropsCache[this.type];
1610
1561
  } // Check if a field is exempt from mapping depth limits
1611
1562
  // Supports deep references like "group.permissions"
1612
1563
 
@@ -1816,18 +1767,11 @@ class SpiceModel {
1816
1767
  addModifier(_ref18) {
1817
1768
  var {
1818
1769
  when,
1819
- execute,
1820
- field = null,
1821
- sourceField = null
1770
+ execute
1822
1771
  } = _ref18;
1823
1772
 
1824
1773
  if (this[_serializers][when]) {
1825
- // Store as object with field info for column-based filtering
1826
- this[_serializers][when]["modifiers"].push({
1827
- execute,
1828
- field,
1829
- sourceField
1830
- });
1774
+ this[_serializers][when]["modifiers"].push(execute);
1831
1775
  }
1832
1776
  }
1833
1777
 
@@ -1856,21 +1800,15 @@ class SpiceModel {
1856
1800
  switch (properties[i].map.type) {
1857
1801
  case _2.MapType.MODEL:
1858
1802
  {
1859
- var destinationField = properties[i].map.destination || i;
1860
-
1861
1803
  switch (properties[i].type) {
1862
1804
  case String:
1863
1805
  case "string":
1864
1806
  {
1865
1807
  _this17.addModifier({
1866
1808
  when: properties[i].map.when || "read",
1867
- field: destinationField,
1868
- // ⚡ Track which field this modifier populates
1869
- sourceField: i,
1870
- // ⚡ Track source property for column filtering
1871
1809
  execute: function () {
1872
1810
  var _execute = _asyncToGenerator(function* (data) {
1873
- 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]);
1874
1812
  });
1875
1813
 
1876
1814
  function execute(_x) {
@@ -1889,13 +1827,9 @@ class SpiceModel {
1889
1827
  {
1890
1828
  _this17.addModifier({
1891
1829
  when: properties[i].map.when || "read",
1892
- field: destinationField,
1893
- // ⚡ Track which field this modifier populates
1894
- sourceField: i,
1895
- // ⚡ Track source property for column filtering
1896
1830
  execute: function () {
1897
1831
  var _execute2 = _asyncToGenerator(function* (data) {
1898
- 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]);
1899
1833
  });
1900
1834
 
1901
1835
  function execute(_x2) {
@@ -1924,42 +1858,6 @@ class SpiceModel {
1924
1858
  for (var i in properties) {
1925
1859
  _loop(i);
1926
1860
  }
1927
- } // ⚡ Parse columns string into a Set for fast lookup
1928
-
1929
-
1930
- parseRequestedColumns(columns) {
1931
- if (!columns || columns === "") return null; // Handle array of columns (convert to string)
1932
-
1933
- if (Array.isArray(columns)) {
1934
- columns = columns.join(",");
1935
- } // Must be a string to parse
1936
-
1937
-
1938
- if (typeof columns !== "string") return null; // Extract field names from column specifications
1939
- // Handles: "field", "`field`", "table.field", "`table`.`field`", "expr AS alias"
1940
-
1941
- var fields = new Set();
1942
- var columnList = columns.split(",").map(c => c.trim());
1943
-
1944
- for (var col of columnList) {
1945
- // Check for AS alias
1946
- var aliasMatch = col.match(/\s+AS\s+`?([\w]+)`?$/i);
1947
-
1948
- if (aliasMatch) {
1949
- fields.add(aliasMatch[1]);
1950
- continue;
1951
- } // Get the last part after dots (the field name)
1952
-
1953
-
1954
- var parts = col.replace(/`/g, "").split(".");
1955
- var fieldName = parts[parts.length - 1];
1956
-
1957
- if (fieldName && fieldName !== "*") {
1958
- fields.add(fieldName);
1959
- }
1960
- }
1961
-
1962
- return fields.size > 0 ? fields : null;
1963
1861
  }
1964
1862
 
1965
1863
  do_serialize(data, type, old_data, args, path_to_be_removed) {
@@ -1991,28 +1889,14 @@ class SpiceModel {
1991
1889
  _this18.addExternalModifiers(_this18.type);
1992
1890
 
1993
1891
  _this18[_external_modifier_loaded] = true;
1994
- } // OPTIMIZED: Parse requested columns for selective modifier execution
1995
-
1892
+ } // Cache the modifiers lookup for the specified type.
1996
1893
 
1997
- var requestedColumns = _this18.parseRequestedColumns(args == null ? void 0 : args.columns); // Cache the modifiers lookup for the specified type.
1998
1894
 
1999
-
2000
- 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) || [];
2001
1896
 
2002
1897
  for (var modifier of modifiers) {
2003
- // Skip field-specific modifiers if columns specified and field is not requested
2004
- if (requestedColumns && modifier.field && !requestedColumns.has(modifier.field) && !(modifier.sourceField && requestedColumns.has(modifier.sourceField))) {
2005
- continue;
2006
- }
2007
-
2008
1898
  try {
2009
- // Handle both function modifiers and object modifiers with .execute
2010
- var executeFn = typeof modifier === "function" ? modifier : modifier.execute;
2011
- var result = yield executeFn(data, old_data, _this18[_ctx], _this18.type); // Only assign if modifier returned a value to prevent data corruption
2012
-
2013
- if (result !== undefined) {
2014
- data = result;
2015
- }
1899
+ data = yield modifier(data, old_data, _this18[_ctx], _this18.type);
2016
1900
  } catch (error) {
2017
1901
  console.error("Modifier error in do_serialize:", error.stack);
2018
1902
  }
@@ -2023,42 +1907,33 @@ class SpiceModel {
2023
1907
 
2024
1908
  if (!originalIsArray) {
2025
1909
  data = [data];
2026
- } // OPTIMIZED: Use cached defaults metadata instead of computing every time
2027
-
2028
-
2029
- var {
2030
- staticDefaults,
2031
- dynamicDefaults,
2032
- hasDefaults
2033
- } = _this18.getDefaultsMetadata(type);
2034
-
2035
- if (hasDefaults) {
2036
- data = data.map(item => {
2037
- // Apply static defaults first (fast - no function calls)
2038
- var result = _.defaults({}, item, staticDefaults); // Only compute dynamic defaults if there are any
2039
-
2040
-
2041
- for (var {
2042
- key,
2043
- fn
2044
- } of dynamicDefaults) {
2045
- if (result[key] === undefined) {
2046
- result[key] = fn({
2047
- old_data: data,
2048
- new_data: old_data
2049
- });
2050
- }
2051
- }
1910
+ } // Compute the defaults from properties using reduce.
2052
1911
 
2053
- return result;
2054
- });
2055
- } // If type is "read", clean the data by omitting certain props.
2056
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.
2057
1929
 
2058
1930
  if (type === "read") {
2059
- // OPTIMIZED: Use cached hidden props instead of computing every time
2060
- 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;
2061
1934
 
1935
+ return (_this18$props$key2 = _this18.props[key]) == null ? void 0 : _this18$props$key2.hide;
1936
+ }); // Combine default props to remove.
2062
1937
 
2063
1938
  var propsToClean = ["deleted", "type", ...path_to_be_removed, ...hiddenProps];
2064
1939
  data = data.map(item => _.omit(item, propsToClean));
@@ -2090,6 +1965,4 @@ class SpiceModel {
2090
1965
 
2091
1966
  }
2092
1967
 
2093
- exports.default = SpiceModel;
2094
- SpiceModel._defaultsCache = {};
2095
- 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.3",
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 =
@@ -1391,47 +1387,6 @@ export default class SpiceModel {
1391
1387
  return true;
1392
1388
  }
1393
1389
 
1394
- // ⚡ OPTIMIZED: Cache defaults metadata per model type
1395
- getDefaultsMetadata(type) {
1396
- const cacheKey = `${this.type}::${type}`;
1397
-
1398
- if (!SpiceModel._defaultsCache[cacheKey]) {
1399
- const staticDefaults = {};
1400
- const dynamicDefaults = [];
1401
-
1402
- // Pre-compute once per model type
1403
- for (const key in this.props) {
1404
- const def = this.props[key]?.defaults?.[type];
1405
- if (def !== undefined) {
1406
- if (_.isFunction(def)) {
1407
- dynamicDefaults.push({ key, fn: def });
1408
- } else {
1409
- staticDefaults[key] = def;
1410
- }
1411
- }
1412
- }
1413
-
1414
- SpiceModel._defaultsCache[cacheKey] = {
1415
- staticDefaults,
1416
- dynamicDefaults,
1417
- hasDefaults:
1418
- Object.keys(staticDefaults).length > 0 || dynamicDefaults.length > 0,
1419
- };
1420
- }
1421
-
1422
- return SpiceModel._defaultsCache[cacheKey];
1423
- }
1424
-
1425
- // ⚡ OPTIMIZED: Cache hidden props per model type
1426
- getHiddenProps() {
1427
- if (!SpiceModel._hiddenPropsCache[this.type]) {
1428
- SpiceModel._hiddenPropsCache[this.type] = Object.keys(this.props).filter(
1429
- (key) => this.props[key]?.hide
1430
- );
1431
- }
1432
- return SpiceModel._hiddenPropsCache[this.type];
1433
- }
1434
-
1435
1390
  // Check if a field is exempt from mapping depth limits
1436
1391
  // Supports deep references like "group.permissions"
1437
1392
  isFieldExempt(source_property) {
@@ -1634,14 +1589,9 @@ export default class SpiceModel {
1634
1589
  return original_is_array ? data : data[0];
1635
1590
  }
1636
1591
 
1637
- addModifier({ when, execute, field = null, sourceField = null }) {
1592
+ addModifier({ when, execute }) {
1638
1593
  if (this[_serializers][when]) {
1639
- // Store as object with field info for column-based filtering
1640
- this[_serializers][when]["modifiers"].push({
1641
- execute,
1642
- field,
1643
- sourceField,
1644
- });
1594
+ this[_serializers][when]["modifiers"].push(execute);
1645
1595
  }
1646
1596
  }
1647
1597
 
@@ -1662,14 +1612,11 @@ export default class SpiceModel {
1662
1612
  if (properties[i].map) {
1663
1613
  switch (properties[i].map.type) {
1664
1614
  case MapType.MODEL: {
1665
- const destinationField = properties[i].map.destination || i;
1666
1615
  switch (properties[i].type) {
1667
1616
  case String:
1668
1617
  case "string": {
1669
1618
  this.addModifier({
1670
1619
  when: properties[i].map.when || "read",
1671
- field: destinationField, // ⚡ Track which field this modifier populates
1672
- sourceField: i, // ⚡ Track source property for column filtering
1673
1620
  execute: async (data) => {
1674
1621
  return await this.mapToObject(
1675
1622
  data,
@@ -1677,7 +1624,7 @@ export default class SpiceModel {
1677
1624
  spice.models[properties[i].map.reference]
1678
1625
  : properties[i].map.reference,
1679
1626
  i,
1680
- destinationField,
1627
+ properties[i].map.destination || i,
1681
1628
  properties[i]
1682
1629
  );
1683
1630
  },
@@ -1688,8 +1635,6 @@ export default class SpiceModel {
1688
1635
  case "array": {
1689
1636
  this.addModifier({
1690
1637
  when: properties[i].map.when || "read",
1691
- field: destinationField, // ⚡ Track which field this modifier populates
1692
- sourceField: i, // ⚡ Track source property for column filtering
1693
1638
  execute: async (data) => {
1694
1639
  return await this.mapToObjectArray(
1695
1640
  data,
@@ -1697,7 +1642,7 @@ export default class SpiceModel {
1697
1642
  spice.models[properties[i].map.reference]
1698
1643
  : properties[i].map.reference,
1699
1644
  i,
1700
- destinationField,
1645
+ properties[i].map.destination || i,
1701
1646
  properties[i]
1702
1647
  );
1703
1648
  },
@@ -1715,41 +1660,6 @@ export default class SpiceModel {
1715
1660
  }
1716
1661
  }
1717
1662
 
1718
- // ⚡ Parse columns string into a Set for fast lookup
1719
- parseRequestedColumns(columns) {
1720
- if (!columns || columns === "") return null;
1721
-
1722
- // Handle array of columns (convert to string)
1723
- if (Array.isArray(columns)) {
1724
- columns = columns.join(",");
1725
- }
1726
-
1727
- // Must be a string to parse
1728
- if (typeof columns !== "string") return null;
1729
- // Extract field names from column specifications
1730
- // Handles: "field", "`field`", "table.field", "`table`.`field`", "expr AS alias"
1731
- const fields = new Set();
1732
- const columnList = columns.split(",").map((c) => c.trim());
1733
-
1734
- for (const col of columnList) {
1735
- // Check for AS alias
1736
- const aliasMatch = col.match(/\s+AS\s+`?([\w]+)`?$/i);
1737
- if (aliasMatch) {
1738
- fields.add(aliasMatch[1]);
1739
- continue;
1740
- }
1741
-
1742
- // Get the last part after dots (the field name)
1743
- const parts = col.replace(/`/g, "").split(".");
1744
- const fieldName = parts[parts.length - 1];
1745
- if (fieldName && fieldName !== "*") {
1746
- fields.add(fieldName);
1747
- }
1748
- }
1749
-
1750
- return fields.size > 0 ? fields : null;
1751
- }
1752
-
1753
1663
  async do_serialize(data, type, old_data, args, path_to_be_removed = []) {
1754
1664
  // Profiling: use track() for proper async context forking
1755
1665
  const p = this[_ctx]?.profiler;
@@ -1766,33 +1676,11 @@ export default class SpiceModel {
1766
1676
  this[_external_modifier_loaded] = true;
1767
1677
  }
1768
1678
 
1769
- // ⚡ OPTIMIZED: Parse requested columns for selective modifier execution
1770
- const requestedColumns = this.parseRequestedColumns(args?.columns);
1771
-
1772
1679
  // Cache the modifiers lookup for the specified type.
1773
1680
  const modifiers = this[_serializers]?.[type]?.modifiers || [];
1774
-
1775
- // Run modifiers serially
1776
1681
  for (const modifier of modifiers) {
1777
- // Skip field-specific modifiers if columns specified and field is not requested
1778
- if (
1779
- requestedColumns &&
1780
- modifier.field &&
1781
- !requestedColumns.has(modifier.field) &&
1782
- !(modifier.sourceField && requestedColumns.has(modifier.sourceField))
1783
- ) {
1784
- continue;
1785
- }
1786
-
1787
1682
  try {
1788
- // Handle both function modifiers and object modifiers with .execute
1789
- const executeFn =
1790
- typeof modifier === "function" ? modifier : modifier.execute;
1791
- const result = await executeFn(data, old_data, this[_ctx], this.type);
1792
- // Only assign if modifier returned a value to prevent data corruption
1793
- if (result !== undefined) {
1794
- data = result;
1795
- }
1683
+ data = await modifier(data, old_data, this[_ctx], this.type);
1796
1684
  } catch (error) {
1797
1685
  console.error("Modifier error in do_serialize:", error.stack);
1798
1686
  }
@@ -1804,30 +1692,27 @@ export default class SpiceModel {
1804
1692
  data = [data];
1805
1693
  }
1806
1694
 
1807
- // OPTIMIZED: Use cached defaults metadata instead of computing every time
1808
- const { staticDefaults, dynamicDefaults, hasDefaults } =
1809
- this.getDefaultsMetadata(type);
1810
-
1811
- if (hasDefaults) {
1812
- data = data.map((item) => {
1813
- // Apply static defaults first (fast - no function calls)
1814
- const result = _.defaults({}, item, staticDefaults);
1815
-
1816
- // Only compute dynamic defaults if there are any
1817
- for (const { key, fn } of dynamicDefaults) {
1818
- if (result[key] === undefined) {
1819
- result[key] = fn({ old_data: data, new_data: old_data });
1820
- }
1821
- }
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
+ }, {});
1822
1706
 
1823
- return result;
1824
- });
1825
- }
1707
+ // Merge defaults into each object.
1708
+ data = data.map((item) => _.defaults(item, defaults));
1826
1709
 
1827
1710
  // If type is "read", clean the data by omitting certain props.
1828
1711
  if (type === "read") {
1829
- // OPTIMIZED: Use cached hidden props instead of computing every time
1830
- 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
+ );
1831
1716
  // Combine default props to remove.
1832
1717
  const propsToClean = [
1833
1718
  "deleted",