spice-js 2.6.72 → 2.6.74

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spice-js",
3
- "version": "2.6.72",
3
+ "version": "2.6.74",
4
4
  "description": "spice",
5
5
  "main": "build/index.js",
6
6
  "repository": {
@@ -486,10 +486,10 @@ export default class SpiceModel {
486
486
  }
487
487
 
488
488
  async get(args) {
489
- // Profiling: zero overhead when disabled (single falsy check)
490
- const p = this[_ctx]?.profiler,
491
- c = p?.start(`${this.type}.get`, { id: args?.id });
492
- try {
489
+ // Profiling: use track() for proper async context forking
490
+ const p = this[_ctx]?.profiler;
491
+
492
+ const doGet = async () => {
493
493
  if (args.mapping_dept) this[_mapping_dept] = args.mapping_dept;
494
494
 
495
495
  if (!args) {
@@ -499,7 +499,8 @@ export default class SpiceModel {
499
499
  if (args.skip_hooks !== true) {
500
500
  await this.run_hook(args, "get", "before");
501
501
  }
502
- let key = `get::${this.type}::${args.id}`;
502
+ // ⚡ Include columns in cache key if specified
503
+ let key = `get::${this.type}::${args.id}${args.columns ? `::${args.columns}` : ""}`;
503
504
  let results = {};
504
505
 
505
506
  if (this.shouldUseCache(this.type)) {
@@ -515,12 +516,12 @@ export default class SpiceModel {
515
516
 
516
517
  if (isCacheEmpty || shouldRefresh) {
517
518
  // Retrieve from the database and update cache
518
- const pDb = p,
519
- cDb = pDb?.start(`${this.type}.get.database`);
520
- try {
519
+ if (p) {
520
+ results = await p.track(`${this.type}.get.database`, async () => {
521
+ return await this.database.get(args.id);
522
+ });
523
+ } else {
521
524
  results = await this.database.get(args.id);
522
- } finally {
523
- cDb && pDb.end(cDb);
524
525
  }
525
526
  await this.getCacheProviderObject(this.type).set(
526
527
  key,
@@ -533,12 +534,12 @@ export default class SpiceModel {
533
534
  }
534
535
  } else {
535
536
  // Directly fetch from the database if caching is disabled
536
- const pDb = p,
537
- cDb = pDb?.start(`${this.type}.get.database`);
538
- try {
537
+ if (p) {
538
+ results = await p.track(`${this.type}.get.database`, async () => {
539
+ return await this.database.get(args.id);
540
+ });
541
+ } else {
539
542
  results = await this.database.get(args.id);
540
- } finally {
541
- cDb && pDb.end(cDb);
542
543
  }
543
544
  }
544
545
 
@@ -553,6 +554,7 @@ export default class SpiceModel {
553
554
  args.skip_read_serialize !== true &&
554
555
  args.skip_serialize !== true
555
556
  ) {
557
+ // ⚡ Pass columns to do_serialize so it can skip irrelevant modifiers
556
558
  results = await this.do_serialize(
557
559
  results,
558
560
  "read",
@@ -561,6 +563,12 @@ export default class SpiceModel {
561
563
  await this.propsToBeRemoved(results)
562
564
  );
563
565
  }
566
+
567
+ // ⚡ OPTIMIZED: Filter results by columns if specified
568
+ if (args.columns) {
569
+ results = this.filterResultsByColumns([results], args.columns)[0];
570
+ }
571
+
564
572
  if (args.skip_hooks !== true) {
565
573
  await this.run_hook(results, "get", "after");
566
574
  }
@@ -569,11 +577,16 @@ export default class SpiceModel {
569
577
  throw new Error(`${this.type} does not exist`);
570
578
  }
571
579
  }
580
+ };
581
+
582
+ try {
583
+ if (p) {
584
+ return await p.track(`${this.type}.get`, doGet, { id: args?.id });
585
+ }
586
+ return await doGet();
572
587
  } catch (e) {
573
588
  console.log(e.message, e);
574
589
  throw e;
575
- } finally {
576
- c && p.end(c);
577
590
  }
578
591
  }
579
592
 
@@ -670,10 +683,10 @@ export default class SpiceModel {
670
683
  }
671
684
 
672
685
  async update(args) {
673
- // Profiling: zero overhead when disabled (single falsy check)
674
- const p = this[_ctx]?.profiler,
675
- c = p?.start(`${this.type}.update`, { id: args?.id });
676
- try {
686
+ // Profiling: use track() for proper async context forking
687
+ const p = this[_ctx]?.profiler;
688
+
689
+ const doUpdate = async () => {
677
690
  this.updated_at = new SDate().now();
678
691
  let results = await this.database.get(args.id);
679
692
  let item_exist = await this.exist(results);
@@ -703,12 +716,12 @@ export default class SpiceModel {
703
716
  }
704
717
  let db_data = cover_obj.new || this;
705
718
 
706
- const pDb = p,
707
- cDb = pDb?.start(`${this.type}.update.database`);
708
- try {
719
+ if (p) {
720
+ await p.track(`${this.type}.update.database`, async () => {
721
+ await this.database.update(args.id, db_data, args._ttl);
722
+ });
723
+ } else {
709
724
  await this.database.update(args.id, db_data, args._ttl);
710
- } finally {
711
- cDb && pDb.end(cDb);
712
725
  }
713
726
  this.setMonitor();
714
727
 
@@ -735,19 +748,24 @@ export default class SpiceModel {
735
748
  }
736
749
  this.id = args.id;
737
750
  return { ...cover_obj.new, id: args.id };
751
+ };
752
+
753
+ try {
754
+ if (p) {
755
+ return await p.track(`${this.type}.update`, doUpdate, { id: args?.id });
756
+ }
757
+ return await doUpdate();
738
758
  } catch (e) {
739
759
  console.log("Error on update", e, e.stack);
740
760
  throw e;
741
- } finally {
742
- c && p.end(c);
743
761
  }
744
762
  }
745
763
 
746
764
  async create(args = {}) {
747
- // Profiling: zero overhead when disabled (single falsy check)
748
- const p = this[_ctx]?.profiler,
749
- c = p?.start(`${this.type}.create`);
750
- try {
765
+ // Profiling: use track() for proper async context forking
766
+ const p = this[_ctx]?.profiler;
767
+
768
+ const doCreate = async () => {
751
769
  let form;
752
770
  this.created_at = new SDate().now();
753
771
  args = _.defaults(args, { id_prefix: this.type });
@@ -773,13 +791,13 @@ export default class SpiceModel {
773
791
  await this.run_hook(workingForm, "create", "before");
774
792
  }
775
793
 
776
- const pDb = p,
777
- cDb = pDb?.start(`${this.type}.create.database`);
778
794
  let results;
779
- try {
795
+ if (p) {
796
+ results = await p.track(`${this.type}.create.database`, async () => {
797
+ return await this.database.insert(id, workingForm, args._ttl);
798
+ });
799
+ } else {
780
800
  results = await this.database.insert(id, workingForm, args._ttl);
781
- } finally {
782
- cDb && pDb.end(cDb);
783
801
  }
784
802
  this.setMonitor();
785
803
 
@@ -803,11 +821,16 @@ export default class SpiceModel {
803
821
  );
804
822
  }
805
823
  return { ...results, id };
824
+ };
825
+
826
+ try {
827
+ if (p) {
828
+ return await p.track(`${this.type}.create`, doCreate);
829
+ }
830
+ return await doCreate();
806
831
  } catch (e) {
807
832
  console.log(e.stack);
808
833
  throw e;
809
- } finally {
810
- c && p.end(c);
811
834
  }
812
835
  }
813
836
 
@@ -828,10 +851,10 @@ export default class SpiceModel {
828
851
  }
829
852
 
830
853
  async delete(args) {
831
- // Profiling: zero overhead when disabled (single falsy check)
832
- const p = this[_ctx]?.profiler,
833
- c = p?.start(`${this.type}.delete`, { id: args?.id });
834
- try {
854
+ // Profiling: use track() for proper async context forking
855
+ const p = this[_ctx]?.profiler;
856
+
857
+ const doDelete = async () => {
835
858
  let item_exist = await this.exist(args.id);
836
859
 
837
860
  if (!item_exist) {
@@ -842,9 +865,8 @@ export default class SpiceModel {
842
865
  await this.run_hook(args, "delete", "before");
843
866
  }
844
867
  let delete_response = {};
845
- const pDb = p,
846
- cDb = pDb?.start(`${this.type}.delete.database`);
847
- try {
868
+
869
+ const dbOperation = async () => {
848
870
  if (args.hard) {
849
871
  delete_response = await this.database.delete(args.id);
850
872
  this.setMonitor();
@@ -853,18 +875,28 @@ export default class SpiceModel {
853
875
  results.deleted = true;
854
876
  delete_response = await this.database.update(args.id, results);
855
877
  }
856
- } finally {
857
- cDb && pDb.end(cDb);
878
+ };
879
+
880
+ if (p) {
881
+ await p.track(`${this.type}.delete.database`, dbOperation);
882
+ } else {
883
+ await dbOperation();
858
884
  }
885
+
859
886
  if (args.skip_hooks != true) {
860
887
  await this.run_hook(results, "delete", "after", results);
861
888
  }
862
889
  return {};
890
+ };
891
+
892
+ try {
893
+ if (p) {
894
+ return await p.track(`${this.type}.delete`, doDelete, { id: args?.id });
895
+ }
896
+ return await doDelete();
863
897
  } catch (e) {
864
898
  console.log(e.stack);
865
899
  throw e;
866
- } finally {
867
- c && p.end(c);
868
900
  }
869
901
  }
870
902
 
@@ -1107,13 +1139,10 @@ export default class SpiceModel {
1107
1139
  }
1108
1140
 
1109
1141
  async list(args = {}) {
1110
- // Profiling: zero overhead when disabled (single falsy check)
1111
- const p = this[_ctx]?.profiler,
1112
- c = p?.start(`${this.type}.list`, {
1113
- limit: args?.limit,
1114
- offset: args?.offset,
1115
- });
1116
- try {
1142
+ // Profiling: use track() for proper async context forking
1143
+ const p = this[_ctx]?.profiler;
1144
+
1145
+ const doList = async () => {
1117
1146
  if (args.mapping_dept) this[_mapping_dept] = args.mapping_dept;
1118
1147
 
1119
1148
  // Find alias tokens from query/columns/sort
@@ -1241,19 +1270,27 @@ export default class SpiceModel {
1241
1270
 
1242
1271
  results.data = this.filterResultsByColumns(results.data, args.columns);
1243
1272
  return results;
1273
+ };
1274
+
1275
+ try {
1276
+ if (p) {
1277
+ return await p.track(`${this.type}.list`, doList, {
1278
+ limit: args?.limit,
1279
+ offset: args?.offset,
1280
+ });
1281
+ }
1282
+ return await doList();
1244
1283
  } catch (e) {
1245
1284
  console.log(e.stack);
1246
1285
  throw e;
1247
- } finally {
1248
- c && p.end(c);
1249
1286
  }
1250
1287
  }
1251
1288
 
1252
1289
  async fetchResults(args, query) {
1253
- // Profiling: zero overhead when disabled (single falsy check)
1254
- const p = this[_ctx]?.profiler,
1255
- c = p?.start(`${this.type}.fetchResults`);
1256
- try {
1290
+ // Profiling: use track() for proper async context forking
1291
+ const p = this[_ctx]?.profiler;
1292
+
1293
+ const doFetch = async () => {
1257
1294
  if (args.is_custom_query === "true" && args.ids.length > 0) {
1258
1295
  return await this.database.query(query);
1259
1296
  } else if (args.is_full_text === "true") {
@@ -1278,9 +1315,12 @@ export default class SpiceModel {
1278
1315
  );
1279
1316
  return result;
1280
1317
  }
1281
- } finally {
1282
- c && p.end(c);
1318
+ };
1319
+
1320
+ if (p) {
1321
+ return await p.track(`${this.type}.fetchResults`, doFetch);
1283
1322
  }
1323
+ return await doFetch();
1284
1324
  }
1285
1325
 
1286
1326
  addHook({ operation, when, execute }) {
@@ -1501,9 +1541,10 @@ export default class SpiceModel {
1501
1541
  return original_is_array ? data : data[0];
1502
1542
  }
1503
1543
 
1504
- addModifier({ when, execute }) {
1544
+ addModifier({ when, execute, field = null }) {
1505
1545
  if (this[_serializers][when]) {
1506
- this[_serializers][when]["modifiers"].push(execute);
1546
+ // Store as object with field info for column-based filtering
1547
+ this[_serializers][when]["modifiers"].push({ execute, field });
1507
1548
  }
1508
1549
  }
1509
1550
 
@@ -1524,11 +1565,13 @@ export default class SpiceModel {
1524
1565
  if (properties[i].map) {
1525
1566
  switch (properties[i].map.type) {
1526
1567
  case MapType.MODEL: {
1568
+ const destinationField = properties[i].map.destination || i;
1527
1569
  switch (properties[i].type) {
1528
1570
  case String:
1529
1571
  case "string": {
1530
1572
  this.addModifier({
1531
1573
  when: properties[i].map.when || "read",
1574
+ field: destinationField, // ⚡ Track which field this modifier populates
1532
1575
  execute: async (data) => {
1533
1576
  return await this.mapToObject(
1534
1577
  data,
@@ -1536,7 +1579,7 @@ export default class SpiceModel {
1536
1579
  spice.models[properties[i].map.reference]
1537
1580
  : properties[i].map.reference,
1538
1581
  i,
1539
- properties[i].map.destination || i,
1582
+ destinationField,
1540
1583
  properties[i]
1541
1584
  );
1542
1585
  },
@@ -1547,6 +1590,7 @@ export default class SpiceModel {
1547
1590
  case "array": {
1548
1591
  this.addModifier({
1549
1592
  when: properties[i].map.when || "read",
1593
+ field: destinationField, // ⚡ Track which field this modifier populates
1550
1594
  execute: async (data) => {
1551
1595
  return await this.mapToObjectArray(
1552
1596
  data,
@@ -1554,7 +1598,7 @@ export default class SpiceModel {
1554
1598
  spice.models[properties[i].map.reference]
1555
1599
  : properties[i].map.reference,
1556
1600
  i,
1557
- properties[i].map.destination || i,
1601
+ destinationField,
1558
1602
  properties[i]
1559
1603
  );
1560
1604
  },
@@ -1572,11 +1616,39 @@ export default class SpiceModel {
1572
1616
  }
1573
1617
  }
1574
1618
 
1619
+ // ⚡ Parse columns string into a Set for fast lookup
1620
+ parseRequestedColumns(columns) {
1621
+ if (!columns || columns === "") return null;
1622
+
1623
+ // Extract field names from column specifications
1624
+ // Handles: "field", "`field`", "table.field", "`table`.`field`", "expr AS alias"
1625
+ const fields = new Set();
1626
+ const columnList = columns.split(",").map((c) => c.trim());
1627
+
1628
+ for (const col of columnList) {
1629
+ // Check for AS alias
1630
+ const aliasMatch = col.match(/\s+AS\s+`?([\w]+)`?$/i);
1631
+ if (aliasMatch) {
1632
+ fields.add(aliasMatch[1]);
1633
+ continue;
1634
+ }
1635
+
1636
+ // Get the last part after dots (the field name)
1637
+ const parts = col.replace(/`/g, "").split(".");
1638
+ const fieldName = parts[parts.length - 1];
1639
+ if (fieldName && fieldName !== "*") {
1640
+ fields.add(fieldName);
1641
+ }
1642
+ }
1643
+
1644
+ return fields.size > 0 ? fields : null;
1645
+ }
1646
+
1575
1647
  async do_serialize(data, type, old_data, args, path_to_be_removed = []) {
1576
- // Profiling: zero overhead when disabled (single falsy check)
1577
- const p = this[_ctx]?.profiler,
1578
- c = p?.start(`${this.type}.do_serialize`, { type });
1579
- try {
1648
+ // Profiling: use track() for proper async context forking
1649
+ const p = this[_ctx]?.profiler;
1650
+
1651
+ const doSerialize = async () => {
1580
1652
  // Early exit if serialization should not run.
1581
1653
  if (!this.shouldSerializerRun(args, type)) {
1582
1654
  return data;
@@ -1588,11 +1660,21 @@ export default class SpiceModel {
1588
1660
  this[_external_modifier_loaded] = true;
1589
1661
  }
1590
1662
 
1663
+ // ⚡ OPTIMIZED: Parse requested columns for selective modifier execution
1664
+ const requestedColumns = this.parseRequestedColumns(args?.columns);
1665
+
1591
1666
  // Cache the modifiers lookup for the specified type.
1592
1667
  const modifiers = this[_serializers]?.[type]?.modifiers || [];
1593
1668
  for (const modifier of modifiers) {
1594
1669
  try {
1595
- data = await modifier(data, old_data, this[_ctx], this.type);
1670
+ // OPTIMIZED: Skip field-specific modifiers if columns specified and field not requested
1671
+ if (requestedColumns && modifier.field && !requestedColumns.has(modifier.field)) {
1672
+ continue; // Skip this modifier - field not in requested columns
1673
+ }
1674
+
1675
+ // Execute modifier (supports both old function format and new object format)
1676
+ const executeFn = typeof modifier === "function" ? modifier : modifier.execute;
1677
+ data = await executeFn(data, old_data, this[_ctx], this.type);
1596
1678
  } catch (error) {
1597
1679
  console.error("Modifier error in do_serialize:", error.stack);
1598
1680
  }
@@ -1640,11 +1722,18 @@ export default class SpiceModel {
1640
1722
 
1641
1723
  // Return in the original format (array or single object).
1642
1724
  return originalIsArray ? data : data[0];
1725
+ };
1726
+
1727
+ try {
1728
+ if (p) {
1729
+ return await p.track(`${this.type}.do_serialize`, doSerialize, {
1730
+ type,
1731
+ });
1732
+ }
1733
+ return await doSerialize();
1643
1734
  } catch (error) {
1644
1735
  console.error("Error in do_serialize:", error.stack);
1645
1736
  throw error;
1646
- } finally {
1647
- c && p.end(c);
1648
1737
  }
1649
1738
  }
1650
1739
  }