spice-js 2.7.3 → 2.7.5

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,30 +1889,24 @@ 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
1892
+ } // Cache the modifiers lookup for the specified type.
1995
1893
 
1996
1894
 
1997
- var requestedColumns = _this18.parseRequestedColumns(args == null ? void 0 : args.columns); // Cache the modifiers lookup for the specified type.
1895
+ var modifiers = ((_this18$_serializers = _this18[_serializers]) == null ? void 0 : (_this18$_serializers$ = _this18$_serializers[type]) == null ? void 0 : _this18$_serializers$.modifiers) || [];
1998
1896
 
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
2001
-
2002
- 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
- }
1897
+ for (var i = 0; i < modifiers.length; i++) {
1898
+ var modifier = modifiers[i];
2007
1899
 
2008
1900
  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
1901
+ var result = yield modifier(data, old_data, _this18[_ctx], _this18.type); // Guard against modifiers that return undefined
2012
1902
 
2013
1903
  if (result !== undefined) {
2014
1904
  data = result;
1905
+ } else {
1906
+ console.warn("Modifier #" + i + " for type=" + _this18.type + " returned undefined, keeping previous data");
2015
1907
  }
2016
1908
  } catch (error) {
2017
- console.error("Modifier error in do_serialize:", error.stack);
1909
+ console.error("Modifier error in do_serialize (type=" + _this18.type + ", modifier #" + i + "):", error instanceof Error ? error.stack : "Non-Error thrown: " + JSON.stringify(error));
2018
1910
  }
2019
1911
  } // Ensure data is always an array for consistent processing.
2020
1912
 
@@ -2023,42 +1915,33 @@ class SpiceModel {
2023
1915
 
2024
1916
  if (!originalIsArray) {
2025
1917
  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
- }
1918
+ } // Compute the defaults from properties using reduce.
2052
1919
 
2053
- return result;
2054
- });
2055
- } // If type is "read", clean the data by omitting certain props.
2056
1920
 
1921
+ var defaults = Object.keys(_this18.props).reduce((acc, key) => {
1922
+ var _this18$props$key, _this18$props$key$def;
1923
+
1924
+ 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];
1925
+
1926
+ if (def !== undefined) {
1927
+ acc[key] = _.isFunction(def) ? def({
1928
+ old_data: data,
1929
+ new_data: old_data
1930
+ }) : def;
1931
+ }
1932
+
1933
+ return acc;
1934
+ }, {}); // Merge defaults into each object.
1935
+
1936
+ data = data.map(item => _.defaults(item, defaults)); // If type is "read", clean the data by omitting certain props.
2057
1937
 
2058
1938
  if (type === "read") {
2059
- // OPTIMIZED: Use cached hidden props instead of computing every time
2060
- var hiddenProps = _this18.getHiddenProps(); // Combine default props to remove.
1939
+ // Collect hidden properties from schema.
1940
+ var hiddenProps = Object.keys(_this18.props).filter(key => {
1941
+ var _this18$props$key2;
2061
1942
 
1943
+ return (_this18$props$key2 = _this18.props[key]) == null ? void 0 : _this18$props$key2.hide;
1944
+ }); // Combine default props to remove.
2062
1945
 
2063
1946
  var propsToClean = ["deleted", "type", ...path_to_be_removed, ...hiddenProps];
2064
1947
  data = data.map(item => _.omit(item, propsToClean));
@@ -2082,7 +1965,7 @@ class SpiceModel {
2082
1965
 
2083
1966
  return yield doSerialize();
2084
1967
  } catch (error) {
2085
- console.error("Error in do_serialize:", error.stack);
1968
+ console.error("Error in do_serialize:", error instanceof Error ? error.stack : "Non-Error thrown: " + JSON.stringify(error));
2086
1969
  throw error;
2087
1970
  }
2088
1971
  })();
@@ -2090,6 +1973,4 @@ class SpiceModel {
2090
1973
 
2091
1974
  }
2092
1975
 
2093
- exports.default = SpiceModel;
2094
- SpiceModel._defaultsCache = {};
2095
- SpiceModel._hiddenPropsCache = {};
1976
+ 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.5",
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,35 +1676,27 @@ 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
- 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
-
1681
+ for (let i = 0; i < modifiers.length; i++) {
1682
+ const modifier = modifiers[i];
1787
1683
  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
1684
+ const result = await modifier(data, old_data, this[_ctx], this.type);
1685
+ // Guard against modifiers that return undefined
1793
1686
  if (result !== undefined) {
1794
1687
  data = result;
1688
+ } else {
1689
+ console.warn(
1690
+ `Modifier #${i} for type=${this.type} returned undefined, keeping previous data`
1691
+ );
1795
1692
  }
1796
1693
  } catch (error) {
1797
- console.error("Modifier error in do_serialize:", error.stack);
1694
+ console.error(
1695
+ `Modifier error in do_serialize (type=${this.type}, modifier #${i}):`,
1696
+ error instanceof Error ?
1697
+ error.stack
1698
+ : `Non-Error thrown: ${JSON.stringify(error)}`
1699
+ );
1798
1700
  }
1799
1701
  }
1800
1702
 
@@ -1804,30 +1706,27 @@ export default class SpiceModel {
1804
1706
  data = [data];
1805
1707
  }
1806
1708
 
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
- }
1709
+ // Compute the defaults from properties using reduce.
1710
+ const defaults = Object.keys(this.props).reduce((acc, key) => {
1711
+ const def = this.props[key]?.defaults?.[type];
1712
+ if (def !== undefined) {
1713
+ acc[key] =
1714
+ _.isFunction(def) ?
1715
+ def({ old_data: data, new_data: old_data })
1716
+ : def;
1717
+ }
1718
+ return acc;
1719
+ }, {});
1822
1720
 
1823
- return result;
1824
- });
1825
- }
1721
+ // Merge defaults into each object.
1722
+ data = data.map((item) => _.defaults(item, defaults));
1826
1723
 
1827
1724
  // If type is "read", clean the data by omitting certain props.
1828
1725
  if (type === "read") {
1829
- // OPTIMIZED: Use cached hidden props instead of computing every time
1830
- const hiddenProps = this.getHiddenProps();
1726
+ // Collect hidden properties from schema.
1727
+ const hiddenProps = Object.keys(this.props).filter(
1728
+ (key) => this.props[key]?.hide
1729
+ );
1831
1730
  // Combine default props to remove.
1832
1731
  const propsToClean = [
1833
1732
  "deleted",
@@ -1850,7 +1749,12 @@ export default class SpiceModel {
1850
1749
  }
1851
1750
  return await doSerialize();
1852
1751
  } catch (error) {
1853
- console.error("Error in do_serialize:", error.stack);
1752
+ console.error(
1753
+ "Error in do_serialize:",
1754
+ error instanceof Error ?
1755
+ error.stack
1756
+ : `Non-Error thrown: ${JSON.stringify(error)}`
1757
+ );
1854
1758
  throw error;
1855
1759
  }
1856
1760
  }