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/build/models/SpiceModel.js +518 -352
- package/package.json +1 -1
- package/src/models/SpiceModel.js +165 -76
package/package.json
CHANGED
package/src/models/SpiceModel.js
CHANGED
|
@@ -486,10 +486,10 @@ export default class SpiceModel {
|
|
|
486
486
|
}
|
|
487
487
|
|
|
488
488
|
async get(args) {
|
|
489
|
-
// Profiling:
|
|
490
|
-
const p = this[_ctx]?.profiler
|
|
491
|
-
|
|
492
|
-
|
|
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
|
-
|
|
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
|
-
|
|
519
|
-
|
|
520
|
-
|
|
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
|
-
|
|
537
|
-
|
|
538
|
-
|
|
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:
|
|
674
|
-
const p = this[_ctx]?.profiler
|
|
675
|
-
|
|
676
|
-
|
|
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
|
-
|
|
707
|
-
|
|
708
|
-
|
|
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:
|
|
748
|
-
const p = this[_ctx]?.profiler
|
|
749
|
-
|
|
750
|
-
|
|
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
|
-
|
|
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:
|
|
832
|
-
const p = this[_ctx]?.profiler
|
|
833
|
-
|
|
834
|
-
|
|
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
|
-
|
|
846
|
-
|
|
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
|
-
}
|
|
857
|
-
|
|
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:
|
|
1111
|
-
const p = this[_ctx]?.profiler
|
|
1112
|
-
|
|
1113
|
-
|
|
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:
|
|
1254
|
-
const p = this[_ctx]?.profiler
|
|
1255
|
-
|
|
1256
|
-
|
|
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
|
-
}
|
|
1282
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
1577
|
-
const p = this[_ctx]?.profiler
|
|
1578
|
-
|
|
1579
|
-
|
|
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
|
-
|
|
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
|
}
|