spice-js 2.6.71 → 2.6.73

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.71",
3
+ "version": "2.6.73",
4
4
  "description": "spice",
5
5
  "main": "build/index.js",
6
6
  "repository": {
@@ -45,7 +45,7 @@ export default class SpiceModel {
45
45
  // ⚡ Static caches for performance optimization
46
46
  static _defaultsCache = {};
47
47
  static _hiddenPropsCache = {};
48
-
48
+
49
49
  constructor(args = {}) {
50
50
  try {
51
51
  var dbtype =
@@ -169,15 +169,17 @@ export default class SpiceModel {
169
169
  }
170
170
  case Number:
171
171
  case "number": {
172
- this[i] = _.isNumber(args.args[i])
173
- ? args.args[i]
172
+ this[i] =
173
+ _.isNumber(args.args[i]) ?
174
+ args.args[i]
174
175
  : Number(args.args[i]);
175
176
  break;
176
177
  }
177
178
  case Boolean:
178
179
  case "boolean": {
179
- this[i] = _.isBoolean(args.args[i])
180
- ? args.args[i]
180
+ this[i] =
181
+ _.isBoolean(args.args[i]) ?
182
+ args.args[i]
181
183
  : args.args[i] == "true" ||
182
184
  args.args[i] == 1 ||
183
185
  args.args[i] == "True";
@@ -190,15 +192,17 @@ export default class SpiceModel {
190
192
  }
191
193
  case Array:
192
194
  case "array": {
193
- this[i] = _.isArray(args.args[i])
194
- ? args.args[i]
195
+ this[i] =
196
+ _.isArray(args.args[i]) ?
197
+ args.args[i]
195
198
  : JSON.parse(args.args[i]);
196
199
  break;
197
200
  }
198
201
  case Object:
199
202
  case "object": {
200
- this[i] = _.isObject(args.args[i])
201
- ? args.args[i]
203
+ this[i] =
204
+ _.isObject(args.args[i]) ?
205
+ args.args[i]
202
206
  : JSON.parse(args.args[i]);
203
207
  break;
204
208
  }
@@ -482,7 +486,10 @@ export default class SpiceModel {
482
486
  }
483
487
 
484
488
  async get(args) {
485
- try {
489
+ // Profiling: use track() for proper async context forking
490
+ const p = this[_ctx]?.profiler;
491
+
492
+ const doGet = async () => {
486
493
  if (args.mapping_dept) this[_mapping_dept] = args.mapping_dept;
487
494
 
488
495
  if (!args) {
@@ -508,7 +515,13 @@ export default class SpiceModel {
508
515
 
509
516
  if (isCacheEmpty || shouldRefresh) {
510
517
  // Retrieve from the database and update cache
511
- results = await this.database.get(args.id);
518
+ if (p) {
519
+ results = await p.track(`${this.type}.get.database`, async () => {
520
+ return await this.database.get(args.id);
521
+ });
522
+ } else {
523
+ results = await this.database.get(args.id);
524
+ }
512
525
  await this.getCacheProviderObject(this.type).set(
513
526
  key,
514
527
  { value: results, time: new Date().getTime() },
@@ -520,7 +533,13 @@ export default class SpiceModel {
520
533
  }
521
534
  } else {
522
535
  // Directly fetch from the database if caching is disabled
523
- results = await this.database.get(args.id);
536
+ if (p) {
537
+ results = await p.track(`${this.type}.get.database`, async () => {
538
+ return await this.database.get(args.id);
539
+ });
540
+ } else {
541
+ results = await this.database.get(args.id);
542
+ }
524
543
  }
525
544
 
526
545
  if (results.type !== undefined && results.type !== this.type) {
@@ -550,6 +569,13 @@ export default class SpiceModel {
550
569
  throw new Error(`${this.type} does not exist`);
551
570
  }
552
571
  }
572
+ };
573
+
574
+ try {
575
+ if (p) {
576
+ return await p.track(`${this.type}.get`, doGet, { id: args?.id });
577
+ }
578
+ return await doGet();
553
579
  } catch (e) {
554
580
  console.log(e.message, e);
555
581
  throw e;
@@ -649,7 +675,10 @@ export default class SpiceModel {
649
675
  }
650
676
 
651
677
  async update(args) {
652
- try {
678
+ // Profiling: use track() for proper async context forking
679
+ const p = this[_ctx]?.profiler;
680
+
681
+ const doUpdate = async () => {
653
682
  this.updated_at = new SDate().now();
654
683
  let results = await this.database.get(args.id);
655
684
  let item_exist = await this.exist(results);
@@ -679,7 +708,13 @@ export default class SpiceModel {
679
708
  }
680
709
  let db_data = cover_obj.new || this;
681
710
 
682
- await this.database.update(args.id, db_data, args._ttl);
711
+ if (p) {
712
+ await p.track(`${this.type}.update.database`, async () => {
713
+ await this.database.update(args.id, db_data, args._ttl);
714
+ });
715
+ } else {
716
+ await this.database.update(args.id, db_data, args._ttl);
717
+ }
683
718
  this.setMonitor();
684
719
 
685
720
  if (args.skip_read_serialize != true && args.skip_serialize != true) {
@@ -705,6 +740,13 @@ export default class SpiceModel {
705
740
  }
706
741
  this.id = args.id;
707
742
  return { ...cover_obj.new, id: args.id };
743
+ };
744
+
745
+ try {
746
+ if (p) {
747
+ return await p.track(`${this.type}.update`, doUpdate, { id: args?.id });
748
+ }
749
+ return await doUpdate();
708
750
  } catch (e) {
709
751
  console.log("Error on update", e, e.stack);
710
752
  throw e;
@@ -712,7 +754,10 @@ export default class SpiceModel {
712
754
  }
713
755
 
714
756
  async create(args = {}) {
715
- try {
757
+ // Profiling: use track() for proper async context forking
758
+ const p = this[_ctx]?.profiler;
759
+
760
+ const doCreate = async () => {
716
761
  let form;
717
762
  this.created_at = new SDate().now();
718
763
  args = _.defaults(args, { id_prefix: this.type });
@@ -738,7 +783,14 @@ export default class SpiceModel {
738
783
  await this.run_hook(workingForm, "create", "before");
739
784
  }
740
785
 
741
- let results = await this.database.insert(id, workingForm, args._ttl);
786
+ let results;
787
+ if (p) {
788
+ results = await p.track(`${this.type}.create.database`, async () => {
789
+ return await this.database.insert(id, workingForm, args._ttl);
790
+ });
791
+ } else {
792
+ results = await this.database.insert(id, workingForm, args._ttl);
793
+ }
742
794
  this.setMonitor();
743
795
 
744
796
  if (args.skip_read_serialize != true && args.skip_serialize != true) {
@@ -761,6 +813,13 @@ export default class SpiceModel {
761
813
  );
762
814
  }
763
815
  return { ...results, id };
816
+ };
817
+
818
+ try {
819
+ if (p) {
820
+ return await p.track(`${this.type}.create`, doCreate);
821
+ }
822
+ return await doCreate();
764
823
  } catch (e) {
765
824
  console.log(e.stack);
766
825
  throw e;
@@ -784,29 +843,49 @@ export default class SpiceModel {
784
843
  }
785
844
 
786
845
  async delete(args) {
787
- let item_exist = await this.exist(args.id);
846
+ // Profiling: use track() for proper async context forking
847
+ const p = this[_ctx]?.profiler;
788
848
 
789
- if (!item_exist) {
790
- throw new Error(`${this.type} does not exist.`);
791
- }
792
- let results = await this.database.get(args.id);
793
- try {
849
+ const doDelete = async () => {
850
+ let item_exist = await this.exist(args.id);
851
+
852
+ if (!item_exist) {
853
+ throw new Error(`${this.type} does not exist.`);
854
+ }
855
+ let results = await this.database.get(args.id);
794
856
  if (args.skip_hooks != true) {
795
857
  await this.run_hook(args, "delete", "before");
796
858
  }
797
859
  let delete_response = {};
798
- if (args.hard) {
799
- delete_response = await this.database.delete(args.id);
800
- this.setMonitor();
860
+
861
+ const dbOperation = async () => {
862
+ if (args.hard) {
863
+ delete_response = await this.database.delete(args.id);
864
+ this.setMonitor();
865
+ } else {
866
+ delete results["id"];
867
+ results.deleted = true;
868
+ delete_response = await this.database.update(args.id, results);
869
+ }
870
+ };
871
+
872
+ if (p) {
873
+ await p.track(`${this.type}.delete.database`, dbOperation);
801
874
  } else {
802
- delete results["id"];
803
- results.deleted = true;
804
- delete_response = await this.database.update(args.id, results);
875
+ await dbOperation();
805
876
  }
877
+
806
878
  if (args.skip_hooks != true) {
807
879
  await this.run_hook(results, "delete", "after", results);
808
880
  }
809
881
  return {};
882
+ };
883
+
884
+ try {
885
+ if (p) {
886
+ return await p.track(`${this.type}.delete`, doDelete, { id: args?.id });
887
+ }
888
+ return await doDelete();
810
889
  } catch (e) {
811
890
  console.log(e.stack);
812
891
  throw e;
@@ -1017,9 +1096,8 @@ export default class SpiceModel {
1017
1096
  const aliasRegex = /\s+AS\s+`?([\w]+)`?$/i;
1018
1097
  const aliasMatch = col.match(aliasRegex);
1019
1098
  const explicitAlias = aliasMatch ? aliasMatch[1] : null;
1020
- const colWithoutAlias = explicitAlias
1021
- ? col.replace(aliasRegex, "").trim()
1022
- : col;
1099
+ const colWithoutAlias =
1100
+ explicitAlias ? col.replace(aliasRegex, "").trim() : col;
1023
1101
 
1024
1102
  // `table`.`col` or table.col
1025
1103
  const columnRegex = /^`?([\w]+)`?\.`?([\w]+)`?$/;
@@ -1053,7 +1131,10 @@ export default class SpiceModel {
1053
1131
  }
1054
1132
 
1055
1133
  async list(args = {}) {
1056
- try {
1134
+ // Profiling: use track() for proper async context forking
1135
+ const p = this[_ctx]?.profiler;
1136
+
1137
+ const doList = async () => {
1057
1138
  if (args.mapping_dept) this[_mapping_dept] = args.mapping_dept;
1058
1139
 
1059
1140
  // Find alias tokens from query/columns/sort
@@ -1126,13 +1207,14 @@ export default class SpiceModel {
1126
1207
  // LIMIT/OFFSET/SORT
1127
1208
  args.limit = Number(args.limit) || undefined;
1128
1209
  args.offset = Number(args.offset) || 0;
1129
- args.sort = args.sort
1130
- ? args.sort
1210
+ args.sort =
1211
+ args.sort ?
1212
+ args.sort
1131
1213
  .split(",")
1132
1214
  .map((item) =>
1133
- item.includes(".")
1134
- ? item
1135
- : `\`${this.type}\`.${this.formatSortComponent(item)}`
1215
+ item.includes(".") ? item : (
1216
+ `\`${this.type}\`.${this.formatSortComponent(item)}`
1217
+ )
1136
1218
  )
1137
1219
  .join(",")
1138
1220
  : `\`${this.type}\`.created_at DESC`;
@@ -1180,6 +1262,16 @@ export default class SpiceModel {
1180
1262
 
1181
1263
  results.data = this.filterResultsByColumns(results.data, args.columns);
1182
1264
  return results;
1265
+ };
1266
+
1267
+ try {
1268
+ if (p) {
1269
+ return await p.track(`${this.type}.list`, doList, {
1270
+ limit: args?.limit,
1271
+ offset: args?.offset,
1272
+ });
1273
+ }
1274
+ return await doList();
1183
1275
  } catch (e) {
1184
1276
  console.log(e.stack);
1185
1277
  throw e;
@@ -1187,30 +1279,40 @@ export default class SpiceModel {
1187
1279
  }
1188
1280
 
1189
1281
  async fetchResults(args, query) {
1190
- if (args.is_custom_query === "true" && args.ids.length > 0) {
1191
- return await this.database.query(query);
1192
- } else if (args.is_full_text === "true") {
1193
- return await this.database.full_text_search(
1194
- this.type,
1195
- query || "",
1196
- args.limit,
1197
- args.offset,
1198
- args._join
1199
- );
1200
- } else {
1201
- let result = await this.database.search(
1202
- this.type,
1203
- args.columns || "",
1204
- query || "",
1205
- args.limit,
1206
- args.offset,
1207
- args.sort,
1208
- args.do_count,
1209
- args.statement_consistent,
1210
- args._join
1211
- );
1212
- return result;
1282
+ // Profiling: use track() for proper async context forking
1283
+ const p = this[_ctx]?.profiler;
1284
+
1285
+ const doFetch = async () => {
1286
+ if (args.is_custom_query === "true" && args.ids.length > 0) {
1287
+ return await this.database.query(query);
1288
+ } else if (args.is_full_text === "true") {
1289
+ return await this.database.full_text_search(
1290
+ this.type,
1291
+ query || "",
1292
+ args.limit,
1293
+ args.offset,
1294
+ args._join
1295
+ );
1296
+ } else {
1297
+ let result = await this.database.search(
1298
+ this.type,
1299
+ args.columns || "",
1300
+ query || "",
1301
+ args.limit,
1302
+ args.offset,
1303
+ args.sort,
1304
+ args.do_count,
1305
+ args.statement_consistent,
1306
+ args._join
1307
+ );
1308
+ return result;
1309
+ }
1310
+ };
1311
+
1312
+ if (p) {
1313
+ return await p.track(`${this.type}.fetchResults`, doFetch);
1213
1314
  }
1315
+ return await doFetch();
1214
1316
  }
1215
1317
 
1216
1318
  addHook({ operation, when, execute }) {
@@ -1264,11 +1366,11 @@ export default class SpiceModel {
1264
1366
  // ⚡ OPTIMIZED: Cache defaults metadata per model type
1265
1367
  getDefaultsMetadata(type) {
1266
1368
  const cacheKey = `${this.type}::${type}`;
1267
-
1369
+
1268
1370
  if (!SpiceModel._defaultsCache[cacheKey]) {
1269
1371
  const staticDefaults = {};
1270
1372
  const dynamicDefaults = [];
1271
-
1373
+
1272
1374
  // Pre-compute once per model type
1273
1375
  for (const key in this.props) {
1274
1376
  const def = this.props[key]?.defaults?.[type];
@@ -1280,14 +1382,15 @@ export default class SpiceModel {
1280
1382
  }
1281
1383
  }
1282
1384
  }
1283
-
1385
+
1284
1386
  SpiceModel._defaultsCache[cacheKey] = {
1285
1387
  staticDefaults,
1286
1388
  dynamicDefaults,
1287
- hasDefaults: Object.keys(staticDefaults).length > 0 || dynamicDefaults.length > 0
1389
+ hasDefaults:
1390
+ Object.keys(staticDefaults).length > 0 || dynamicDefaults.length > 0,
1288
1391
  };
1289
1392
  }
1290
-
1393
+
1291
1394
  return SpiceModel._defaultsCache[cacheKey];
1292
1395
  }
1293
1396
 
@@ -1461,9 +1564,9 @@ export default class SpiceModel {
1461
1564
  execute: async (data) => {
1462
1565
  return await this.mapToObject(
1463
1566
  data,
1464
- _.isString(properties[i].map.reference)
1465
- ? spice.models[properties[i].map.reference]
1466
- : properties[i].map.reference,
1567
+ _.isString(properties[i].map.reference) ?
1568
+ spice.models[properties[i].map.reference]
1569
+ : properties[i].map.reference,
1467
1570
  i,
1468
1571
  properties[i].map.destination || i,
1469
1572
  properties[i]
@@ -1479,9 +1582,9 @@ export default class SpiceModel {
1479
1582
  execute: async (data) => {
1480
1583
  return await this.mapToObjectArray(
1481
1584
  data,
1482
- _.isString(properties[i].map.reference)
1483
- ? spice.models[properties[i].map.reference]
1484
- : properties[i].map.reference,
1585
+ _.isString(properties[i].map.reference) ?
1586
+ spice.models[properties[i].map.reference]
1587
+ : properties[i].map.reference,
1485
1588
  i,
1486
1589
  properties[i].map.destination || i,
1487
1590
  properties[i]
@@ -1502,7 +1605,10 @@ export default class SpiceModel {
1502
1605
  }
1503
1606
 
1504
1607
  async do_serialize(data, type, old_data, args, path_to_be_removed = []) {
1505
- try {
1608
+ // Profiling: use track() for proper async context forking
1609
+ const p = this[_ctx]?.profiler;
1610
+
1611
+ const doSerialize = async () => {
1506
1612
  // Early exit if serialization should not run.
1507
1613
  if (!this.shouldSerializerRun(args, type)) {
1508
1614
  return data;
@@ -1531,20 +1637,21 @@ export default class SpiceModel {
1531
1637
  }
1532
1638
 
1533
1639
  // ⚡ OPTIMIZED: Use cached defaults metadata instead of computing every time
1534
- const { staticDefaults, dynamicDefaults, hasDefaults } = this.getDefaultsMetadata(type);
1535
-
1640
+ const { staticDefaults, dynamicDefaults, hasDefaults } =
1641
+ this.getDefaultsMetadata(type);
1642
+
1536
1643
  if (hasDefaults) {
1537
1644
  data = data.map((item) => {
1538
1645
  // Apply static defaults first (fast - no function calls)
1539
1646
  const result = _.defaults({}, item, staticDefaults);
1540
-
1647
+
1541
1648
  // Only compute dynamic defaults if there are any
1542
1649
  for (const { key, fn } of dynamicDefaults) {
1543
1650
  if (result[key] === undefined) {
1544
1651
  result[key] = fn({ old_data: data, new_data: old_data });
1545
1652
  }
1546
1653
  }
1547
-
1654
+
1548
1655
  return result;
1549
1656
  });
1550
1657
  }
@@ -1565,6 +1672,15 @@ export default class SpiceModel {
1565
1672
 
1566
1673
  // Return in the original format (array or single object).
1567
1674
  return originalIsArray ? data : data[0];
1675
+ };
1676
+
1677
+ try {
1678
+ if (p) {
1679
+ return await p.track(`${this.type}.do_serialize`, doSerialize, {
1680
+ type,
1681
+ });
1682
+ }
1683
+ return await doSerialize();
1568
1684
  } catch (error) {
1569
1685
  console.error("Error in do_serialize:", error.stack);
1570
1686
  throw error;