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/build/models/SpiceModel.js +483 -269
- package/package.json +1 -1
- package/src/models/SpiceModel.js +192 -76
package/package.json
CHANGED
package/src/models/SpiceModel.js
CHANGED
|
@@ -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] =
|
|
173
|
-
|
|
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] =
|
|
180
|
-
|
|
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] =
|
|
194
|
-
|
|
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] =
|
|
201
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
846
|
+
// Profiling: use track() for proper async context forking
|
|
847
|
+
const p = this[_ctx]?.profiler;
|
|
788
848
|
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
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
|
-
|
|
799
|
-
|
|
800
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
1130
|
-
|
|
1210
|
+
args.sort =
|
|
1211
|
+
args.sort ?
|
|
1212
|
+
args.sort
|
|
1131
1213
|
.split(",")
|
|
1132
1214
|
.map((item) =>
|
|
1133
|
-
item.includes(".")
|
|
1134
|
-
|
|
1135
|
-
|
|
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
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
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:
|
|
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
|
-
|
|
1466
|
-
|
|
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
|
-
|
|
1484
|
-
|
|
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
|
-
|
|
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 } =
|
|
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;
|