spice-js 2.7.30 → 2.7.31
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 +242 -51
- package/package.json +1 -1
- package/src/models/SpiceModel.js +201 -11
- package/tests/models/SpiceModel.mapping-depth.test.js +143 -5
|
@@ -7,9 +7,9 @@ var _ResourceLifecycleTriggered = _interopRequireDefault(require("../events/even
|
|
|
7
7
|
var _Security = require("../utility/Security");
|
|
8
8
|
var _fix = require("../utility/fix");
|
|
9
9
|
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
10
|
+
function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) { ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } } return n; }, _extends.apply(null, arguments); }
|
|
10
11
|
function asyncGeneratorStep(n, t, e, r, o, a, c) { try { var i = n[a](c), u = i.value; } catch (n) { return void e(n); } i.done ? t(u) : Promise.resolve(u).then(r, o); }
|
|
11
12
|
function _asyncToGenerator(n) { return function () { var t = this, e = arguments; return new Promise(function (r, o) { var a = n.apply(t, e); function _next(n) { asyncGeneratorStep(a, r, o, _next, _throw, "next", n); } function _throw(n) { asyncGeneratorStep(a, r, o, _next, _throw, "throw", n); } _next(void 0); }); }; }
|
|
12
|
-
function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) { ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } } return n; }, _extends.apply(null, arguments); }
|
|
13
13
|
var co = require("co");
|
|
14
14
|
var regeneratorRuntime = require("regenerator-runtime");
|
|
15
15
|
var SDate = require("sonover-date"),
|
|
@@ -29,11 +29,74 @@ var SDate = require("sonover-date"),
|
|
|
29
29
|
_mapping_dept = Symbol(),
|
|
30
30
|
_mapping_dept_scalar = Symbol(),
|
|
31
31
|
_mapping_dept_array = Symbol(),
|
|
32
|
+
_mapping_concurrency = Symbol(),
|
|
33
|
+
_skip_mapped_serialization = Symbol(),
|
|
32
34
|
_mapping_dept_exempt = Symbol(),
|
|
33
35
|
_current_path = Symbol();
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
36
|
+
var GLOBAL_MAPPING_CONCURRENCY = Math.max(1, Number(process.env.SPICE_MAPPING_GLOBAL_CONCURRENCY) || 32);
|
|
37
|
+
var activeGlobalMappings = 0;
|
|
38
|
+
var globalMappingWaiters = [];
|
|
39
|
+
function normalizeConcurrency(value, fallback) {
|
|
40
|
+
if (fallback === void 0) {
|
|
41
|
+
fallback = 1;
|
|
42
|
+
}
|
|
43
|
+
var parsed = Number(value);
|
|
44
|
+
return Number.isFinite(parsed) && parsed > 0 ? Math.floor(parsed) : fallback;
|
|
45
|
+
}
|
|
46
|
+
function acquireGlobalMappingSlot() {
|
|
47
|
+
return _acquireGlobalMappingSlot.apply(this, arguments);
|
|
48
|
+
}
|
|
49
|
+
function _acquireGlobalMappingSlot() {
|
|
50
|
+
_acquireGlobalMappingSlot = _asyncToGenerator(function* () {
|
|
51
|
+
var queuedAt = process.hrtime.bigint();
|
|
52
|
+
if (activeGlobalMappings < GLOBAL_MAPPING_CONCURRENCY) {
|
|
53
|
+
activeGlobalMappings += 1;
|
|
54
|
+
} else {
|
|
55
|
+
yield new Promise(resolve => globalMappingWaiters.push(resolve));
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
active: activeGlobalMappings,
|
|
59
|
+
waitMs: Number(process.hrtime.bigint() - queuedAt) / 1e6,
|
|
60
|
+
release() {
|
|
61
|
+
var next = globalMappingWaiters.shift();
|
|
62
|
+
if (next) {
|
|
63
|
+
next();
|
|
64
|
+
} else {
|
|
65
|
+
activeGlobalMappings -= 1;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
});
|
|
70
|
+
return _acquireGlobalMappingSlot.apply(this, arguments);
|
|
71
|
+
}
|
|
72
|
+
function runWithConcurrency(_x, _x2, _x3) {
|
|
73
|
+
return _runWithConcurrency.apply(this, arguments);
|
|
74
|
+
} //const _type = Symbol("type");
|
|
75
|
+
function _runWithConcurrency() {
|
|
76
|
+
_runWithConcurrency = _asyncToGenerator(function* (items, concurrency, worker) {
|
|
77
|
+
var results = new Array(items.length);
|
|
78
|
+
var nextIndex = 0;
|
|
79
|
+
function runWorker() {
|
|
80
|
+
return _runWorker.apply(this, arguments);
|
|
81
|
+
}
|
|
82
|
+
function _runWorker() {
|
|
83
|
+
_runWorker = _asyncToGenerator(function* () {
|
|
84
|
+
while (nextIndex < items.length) {
|
|
85
|
+
var index = nextIndex;
|
|
86
|
+
nextIndex += 1;
|
|
87
|
+
results[index] = yield worker(items[index], index);
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
return _runWorker.apply(this, arguments);
|
|
91
|
+
}
|
|
92
|
+
var workerCount = Math.min(concurrency, items.length);
|
|
93
|
+
yield Promise.all(Array.from({
|
|
94
|
+
length: workerCount
|
|
95
|
+
}, () => runWorker()));
|
|
96
|
+
return results;
|
|
97
|
+
});
|
|
98
|
+
return _runWithConcurrency.apply(this, arguments);
|
|
99
|
+
}
|
|
37
100
|
var that;
|
|
38
101
|
if (!Promise.allSettled) {
|
|
39
102
|
Promise.allSettled = promises => Promise.all(promises.map((promise, i) => promise.then(value => ({
|
|
@@ -50,7 +113,7 @@ class SpiceModel {
|
|
|
50
113
|
args = {};
|
|
51
114
|
}
|
|
52
115
|
try {
|
|
53
|
-
var _args2, _modelArgs$ctx, _modelArgs$ctx$state, _ref, _ref2, _modelArgs$mapping_de, _ref3, _ref4, _modelArgs$mapping_de2, _args$args, _args3, _args3$args, _args4, _args4$args, _args5, _args5$args, _args6, _args6$args, _args7, _args7$args;
|
|
116
|
+
var _args2, _modelArgs$ctx, _modelArgs$ctx$state, _ref, _ref2, _modelArgs$mapping_de, _ref3, _ref4, _modelArgs$mapping_de2, _modelArgs$mapping_co, _ref5, _modelArgs$skip_mappe, _args$args, _args3, _args3$args, _args4, _args4$args, _args5, _args5$args, _args6, _args6$args, _args7, _args7$args;
|
|
54
117
|
var modelArgs = ((_args2 = args) == null ? void 0 : _args2.args) || {};
|
|
55
118
|
var requestProfile = (modelArgs == null ? void 0 : (_modelArgs$ctx = modelArgs.ctx) == null ? void 0 : (_modelArgs$ctx$state = _modelArgs$ctx.state) == null ? void 0 : _modelArgs$ctx$state.read_profile) || {};
|
|
56
119
|
var defaultMappingDept = process.env.MAPPING_DEPT || 3;
|
|
@@ -60,6 +123,8 @@ class SpiceModel {
|
|
|
60
123
|
this[_mapping_dept] = legacyMappingDept != null ? legacyMappingDept : defaultMappingDept;
|
|
61
124
|
this[_mapping_dept_scalar] = (_ref = (_ref2 = (_modelArgs$mapping_de = modelArgs.mapping_dept_scalar) != null ? _modelArgs$mapping_de : legacyMappingDept) != null ? _ref2 : requestProfile.mapping_dept_scalar) != null ? _ref : defaultMappingDept;
|
|
62
125
|
this[_mapping_dept_array] = (_ref3 = (_ref4 = (_modelArgs$mapping_de2 = modelArgs.mapping_dept_array) != null ? _modelArgs$mapping_de2 : legacyMappingDept) != null ? _ref4 : requestProfile.mapping_dept_array) != null ? _ref3 : defaultMappingDept;
|
|
126
|
+
this[_mapping_concurrency] = normalizeConcurrency((_modelArgs$mapping_co = modelArgs.mapping_concurrency) != null ? _modelArgs$mapping_co : requestProfile.mapping_concurrency);
|
|
127
|
+
this[_skip_mapped_serialization] = (_ref5 = (_modelArgs$skip_mappe = modelArgs.skip_mapped_serialization) != null ? _modelArgs$skip_mappe : requestProfile.skip_mapped_serialization) != null ? _ref5 : false;
|
|
63
128
|
this[_mapping_dept_exempt] = modelArgs.mapping_dept_exempt || [];
|
|
64
129
|
this.type = "";
|
|
65
130
|
this.collection = args.connection;
|
|
@@ -233,6 +298,12 @@ class SpiceModel {
|
|
|
233
298
|
if (args.mapping_dept_array !== undefined && args.mapping_dept_array !== null) {
|
|
234
299
|
this[_mapping_dept_array] = args.mapping_dept_array;
|
|
235
300
|
}
|
|
301
|
+
if (args.skip_mapped_serialization !== undefined) {
|
|
302
|
+
this[_skip_mapped_serialization] = args.skip_mapped_serialization;
|
|
303
|
+
}
|
|
304
|
+
if (args.mapping_concurrency !== undefined) {
|
|
305
|
+
this[_mapping_concurrency] = normalizeConcurrency(args.mapping_concurrency, this[_mapping_concurrency]);
|
|
306
|
+
}
|
|
236
307
|
if (args.mapping_dept_exempt) {
|
|
237
308
|
this[_mapping_dept_exempt] = args.mapping_dept_exempt;
|
|
238
309
|
}
|
|
@@ -278,13 +349,13 @@ class SpiceModel {
|
|
|
278
349
|
return [];
|
|
279
350
|
})();
|
|
280
351
|
}
|
|
281
|
-
createValidationString(
|
|
352
|
+
createValidationString(_ref6) {
|
|
282
353
|
var {
|
|
283
354
|
property_name,
|
|
284
355
|
props,
|
|
285
356
|
operation,
|
|
286
357
|
complete
|
|
287
|
-
} =
|
|
358
|
+
} = _ref6;
|
|
288
359
|
var operationExempt = {
|
|
289
360
|
PUT: ["required", "unique"]
|
|
290
361
|
};
|
|
@@ -515,7 +586,7 @@ class SpiceModel {
|
|
|
515
586
|
// Profiling: use track() for proper async context forking
|
|
516
587
|
var p = (_this4$_ctx = _this4[_ctx]) == null ? void 0 : _this4$_ctx.profiler;
|
|
517
588
|
var doGet = /*#__PURE__*/function () {
|
|
518
|
-
var
|
|
589
|
+
var _ref7 = _asyncToGenerator(function* () {
|
|
519
590
|
if (!args) {
|
|
520
591
|
args = {};
|
|
521
592
|
}
|
|
@@ -624,7 +695,7 @@ class SpiceModel {
|
|
|
624
695
|
}
|
|
625
696
|
});
|
|
626
697
|
return function doGet() {
|
|
627
|
-
return
|
|
698
|
+
return _ref7.apply(this, arguments);
|
|
628
699
|
};
|
|
629
700
|
}();
|
|
630
701
|
try {
|
|
@@ -659,7 +730,7 @@ class SpiceModel {
|
|
|
659
730
|
// ⚡ Profiling: use track() for proper async context forking
|
|
660
731
|
var p = (_this6$_ctx = _this6[_ctx]) == null ? void 0 : _this6$_ctx.profiler;
|
|
661
732
|
var doGetMulti = /*#__PURE__*/function () {
|
|
662
|
-
var
|
|
733
|
+
var _ref0 = _asyncToGenerator(function* () {
|
|
663
734
|
if (!args) {
|
|
664
735
|
args = {};
|
|
665
736
|
}
|
|
@@ -701,7 +772,7 @@ class SpiceModel {
|
|
|
701
772
|
return results;
|
|
702
773
|
});
|
|
703
774
|
return function doGetMulti() {
|
|
704
|
-
return
|
|
775
|
+
return _ref0.apply(this, arguments);
|
|
705
776
|
};
|
|
706
777
|
}();
|
|
707
778
|
try {
|
|
@@ -757,7 +828,7 @@ class SpiceModel {
|
|
|
757
828
|
// Profiling: use track() for proper async context forking
|
|
758
829
|
var p = (_this8$_ctx = _this8[_ctx]) == null ? void 0 : _this8$_ctx.profiler;
|
|
759
830
|
var doUpdate = /*#__PURE__*/function () {
|
|
760
|
-
var
|
|
831
|
+
var _ref1 = _asyncToGenerator(function* () {
|
|
761
832
|
_this8.updated_at = new SDate().now();
|
|
762
833
|
var results = yield _this8.database.get(args.id);
|
|
763
834
|
var item_exist = yield _this8.exist(results);
|
|
@@ -801,7 +872,7 @@ class SpiceModel {
|
|
|
801
872
|
});
|
|
802
873
|
});
|
|
803
874
|
return function doUpdate() {
|
|
804
|
-
return
|
|
875
|
+
return _ref1.apply(this, arguments);
|
|
805
876
|
};
|
|
806
877
|
}();
|
|
807
878
|
try {
|
|
@@ -827,7 +898,7 @@ class SpiceModel {
|
|
|
827
898
|
// Profiling: use track() for proper async context forking
|
|
828
899
|
var p = (_this9$_ctx = _this9[_ctx]) == null ? void 0 : _this9$_ctx.profiler;
|
|
829
900
|
var doCreate = /*#__PURE__*/function () {
|
|
830
|
-
var
|
|
901
|
+
var _ref11 = _asyncToGenerator(function* () {
|
|
831
902
|
var form;
|
|
832
903
|
_this9.created_at = new SDate().now();
|
|
833
904
|
args = _.defaults(args, {
|
|
@@ -873,7 +944,7 @@ class SpiceModel {
|
|
|
873
944
|
});
|
|
874
945
|
});
|
|
875
946
|
return function doCreate() {
|
|
876
|
-
return
|
|
947
|
+
return _ref11.apply(this, arguments);
|
|
877
948
|
};
|
|
878
949
|
}();
|
|
879
950
|
try {
|
|
@@ -911,7 +982,7 @@ class SpiceModel {
|
|
|
911
982
|
// Profiling: use track() for proper async context forking
|
|
912
983
|
var p = (_this1$_ctx = _this1[_ctx]) == null ? void 0 : _this1$_ctx.profiler;
|
|
913
984
|
var doDelete = /*#__PURE__*/function () {
|
|
914
|
-
var
|
|
985
|
+
var _ref13 = _asyncToGenerator(function* () {
|
|
915
986
|
var item_exist = yield _this1.exist(args.id);
|
|
916
987
|
if (!item_exist) {
|
|
917
988
|
throw new Error(_this1.type + " does not exist.");
|
|
@@ -922,7 +993,7 @@ class SpiceModel {
|
|
|
922
993
|
}
|
|
923
994
|
var delete_response = {};
|
|
924
995
|
var dbOperation = /*#__PURE__*/function () {
|
|
925
|
-
var
|
|
996
|
+
var _ref14 = _asyncToGenerator(function* () {
|
|
926
997
|
if (args.hard) {
|
|
927
998
|
delete_response = yield _this1.database.delete(args.id);
|
|
928
999
|
_this1.setMonitor();
|
|
@@ -933,7 +1004,7 @@ class SpiceModel {
|
|
|
933
1004
|
}
|
|
934
1005
|
});
|
|
935
1006
|
return function dbOperation() {
|
|
936
|
-
return
|
|
1007
|
+
return _ref14.apply(this, arguments);
|
|
937
1008
|
};
|
|
938
1009
|
}();
|
|
939
1010
|
if (p) {
|
|
@@ -949,7 +1020,7 @@ class SpiceModel {
|
|
|
949
1020
|
return {};
|
|
950
1021
|
});
|
|
951
1022
|
return function doDelete() {
|
|
952
|
-
return
|
|
1023
|
+
return _ref13.apply(this, arguments);
|
|
953
1024
|
};
|
|
954
1025
|
}();
|
|
955
1026
|
try {
|
|
@@ -1139,12 +1210,12 @@ class SpiceModel {
|
|
|
1139
1210
|
}
|
|
1140
1211
|
createJoinSection(mappedNestings) {
|
|
1141
1212
|
if (!mappedNestings || mappedNestings.length === 0) return "";
|
|
1142
|
-
return mappedNestings.map(
|
|
1213
|
+
return mappedNestings.map(_ref15 => {
|
|
1143
1214
|
var {
|
|
1144
1215
|
alias,
|
|
1145
1216
|
reference,
|
|
1146
1217
|
is_array
|
|
1147
|
-
} =
|
|
1218
|
+
} = _ref15;
|
|
1148
1219
|
var keyspace = (0, _fix.fixCollection)(reference);
|
|
1149
1220
|
if (is_array === true) {
|
|
1150
1221
|
return "LEFT NEST `" + keyspace + "` AS `" + alias + "` ON KEYS `" + this.type + "`.`" + alias + "`";
|
|
@@ -1289,7 +1360,7 @@ class SpiceModel {
|
|
|
1289
1360
|
// Profiling: use track() for proper async context forking
|
|
1290
1361
|
var p = (_this10$_ctx = _this10[_ctx]) == null ? void 0 : _this10$_ctx.profiler;
|
|
1291
1362
|
var doList = /*#__PURE__*/function () {
|
|
1292
|
-
var
|
|
1363
|
+
var _ref16 = _asyncToGenerator(function* () {
|
|
1293
1364
|
var _args1, _args10;
|
|
1294
1365
|
_this10.applyMappingDepthArgs(args);
|
|
1295
1366
|
|
|
@@ -1368,7 +1439,7 @@ class SpiceModel {
|
|
|
1368
1439
|
return results;
|
|
1369
1440
|
});
|
|
1370
1441
|
return function doList() {
|
|
1371
|
-
return
|
|
1442
|
+
return _ref16.apply(this, arguments);
|
|
1372
1443
|
};
|
|
1373
1444
|
}();
|
|
1374
1445
|
try {
|
|
@@ -1422,7 +1493,7 @@ class SpiceModel {
|
|
|
1422
1493
|
// Profiling: use track() for proper async context forking
|
|
1423
1494
|
var p = (_this11$_ctx = _this11[_ctx]) == null ? void 0 : _this11$_ctx.profiler;
|
|
1424
1495
|
var doFetch = /*#__PURE__*/function () {
|
|
1425
|
-
var
|
|
1496
|
+
var _ref17 = _asyncToGenerator(function* () {
|
|
1426
1497
|
if (args.is_custom_query === "true" && args.ids.length > 0) {
|
|
1427
1498
|
console.log("Fetching results from database", _this11.type, args, query, mappedNestings);
|
|
1428
1499
|
return yield _this11.database.query(query);
|
|
@@ -1435,7 +1506,7 @@ class SpiceModel {
|
|
|
1435
1506
|
}
|
|
1436
1507
|
});
|
|
1437
1508
|
return function doFetch() {
|
|
1438
|
-
return
|
|
1509
|
+
return _ref17.apply(this, arguments);
|
|
1439
1510
|
};
|
|
1440
1511
|
}();
|
|
1441
1512
|
if (p) {
|
|
@@ -1444,12 +1515,12 @@ class SpiceModel {
|
|
|
1444
1515
|
return yield doFetch();
|
|
1445
1516
|
})();
|
|
1446
1517
|
}
|
|
1447
|
-
addHook(
|
|
1518
|
+
addHook(_ref18) {
|
|
1448
1519
|
var {
|
|
1449
1520
|
operation,
|
|
1450
1521
|
when,
|
|
1451
1522
|
execute
|
|
1452
|
-
} =
|
|
1523
|
+
} = _ref18;
|
|
1453
1524
|
this[_hooks][operation][when].push(execute);
|
|
1454
1525
|
}
|
|
1455
1526
|
run_hook(data, op, when, old_data) {
|
|
@@ -1572,7 +1643,7 @@ class SpiceModel {
|
|
|
1572
1643
|
|
|
1573
1644
|
// ⚡ Wrap in profiler track() to ensure proper async context for child operations
|
|
1574
1645
|
var fetchRelated = /*#__PURE__*/function () {
|
|
1575
|
-
var
|
|
1646
|
+
var _ref19 = _asyncToGenerator(function* () {
|
|
1576
1647
|
return yield Promise.allSettled(_.map(classes, obj => {
|
|
1577
1648
|
var objInstance = new obj(_extends({}, _this13[_args], {
|
|
1578
1649
|
skip_cache: _this13[_skip_cache],
|
|
@@ -1580,6 +1651,8 @@ class SpiceModel {
|
|
|
1580
1651
|
mapping_dept: _this13[_mapping_dept],
|
|
1581
1652
|
mapping_dept_scalar: _this13[_mapping_dept_scalar],
|
|
1582
1653
|
mapping_dept_array: _this13[_mapping_dept_array],
|
|
1654
|
+
mapping_concurrency: _this13[_mapping_concurrency],
|
|
1655
|
+
skip_mapped_serialization: _this13[_skip_mapped_serialization],
|
|
1583
1656
|
mapping_dept_exempt: _this13[_mapping_dept_exempt],
|
|
1584
1657
|
_columns: columnArray.join(","),
|
|
1585
1658
|
_current_path: childPath
|
|
@@ -1587,12 +1660,13 @@ class SpiceModel {
|
|
|
1587
1660
|
return objInstance.getMulti({
|
|
1588
1661
|
skip_hooks: true,
|
|
1589
1662
|
ids: ids,
|
|
1590
|
-
columns: columnArray
|
|
1663
|
+
columns: columnArray,
|
|
1664
|
+
skip_read_serialize: _this13[_skip_mapped_serialization]
|
|
1591
1665
|
});
|
|
1592
1666
|
}));
|
|
1593
1667
|
});
|
|
1594
1668
|
return function fetchRelated() {
|
|
1595
|
-
return
|
|
1669
|
+
return _ref19.apply(this, arguments);
|
|
1596
1670
|
};
|
|
1597
1671
|
}();
|
|
1598
1672
|
var returned_all;
|
|
@@ -1693,7 +1767,7 @@ class SpiceModel {
|
|
|
1693
1767
|
|
|
1694
1768
|
// ⚡ Wrap in profiler track() to ensure proper async context for child operations
|
|
1695
1769
|
var fetchRelated = /*#__PURE__*/function () {
|
|
1696
|
-
var
|
|
1770
|
+
var _ref20 = _asyncToGenerator(function* () {
|
|
1697
1771
|
return yield Promise.allSettled(_.map(classes, obj => {
|
|
1698
1772
|
return new obj(_extends({}, _this14[_args], {
|
|
1699
1773
|
skip_cache: _this14[_skip_cache],
|
|
@@ -1701,18 +1775,21 @@ class SpiceModel {
|
|
|
1701
1775
|
mapping_dept: _this14[_mapping_dept],
|
|
1702
1776
|
mapping_dept_scalar: _this14[_mapping_dept_scalar],
|
|
1703
1777
|
mapping_dept_array: _this14[_mapping_dept_array],
|
|
1778
|
+
mapping_concurrency: _this14[_mapping_concurrency],
|
|
1779
|
+
skip_mapped_serialization: _this14[_skip_mapped_serialization],
|
|
1704
1780
|
mapping_dept_exempt: _this14[_mapping_dept_exempt],
|
|
1705
1781
|
_columns: columnArray.join(","),
|
|
1706
1782
|
_current_path: childPath
|
|
1707
1783
|
})).getMulti({
|
|
1708
1784
|
skip_hooks: true,
|
|
1709
1785
|
ids: ids,
|
|
1710
|
-
columns: columnArray
|
|
1786
|
+
columns: columnArray,
|
|
1787
|
+
skip_read_serialize: _this14[_skip_mapped_serialization]
|
|
1711
1788
|
});
|
|
1712
1789
|
}));
|
|
1713
1790
|
});
|
|
1714
1791
|
return function fetchRelated() {
|
|
1715
|
-
return
|
|
1792
|
+
return _ref20.apply(this, arguments);
|
|
1716
1793
|
};
|
|
1717
1794
|
}();
|
|
1718
1795
|
var returned_all;
|
|
@@ -1760,12 +1837,22 @@ class SpiceModel {
|
|
|
1760
1837
|
return original_is_array ? data : data[0];
|
|
1761
1838
|
})();
|
|
1762
1839
|
}
|
|
1763
|
-
addModifier(
|
|
1840
|
+
addModifier(_ref21) {
|
|
1764
1841
|
var {
|
|
1765
1842
|
when,
|
|
1766
|
-
execute
|
|
1767
|
-
|
|
1843
|
+
execute,
|
|
1844
|
+
kind,
|
|
1845
|
+
source_property,
|
|
1846
|
+
store_property
|
|
1847
|
+
} = _ref21;
|
|
1768
1848
|
if (this[_serializers][when]) {
|
|
1849
|
+
if (kind) {
|
|
1850
|
+
execute.spiceModifier = {
|
|
1851
|
+
kind,
|
|
1852
|
+
source_property,
|
|
1853
|
+
store_property
|
|
1854
|
+
};
|
|
1855
|
+
}
|
|
1769
1856
|
this[_serializers][when]["modifiers"].push(execute);
|
|
1770
1857
|
}
|
|
1771
1858
|
}
|
|
@@ -1833,6 +1920,9 @@ class SpiceModel {
|
|
|
1833
1920
|
{
|
|
1834
1921
|
_this15.addModifier({
|
|
1835
1922
|
when: properties[i].map.when || "read",
|
|
1923
|
+
kind: "relationship_map",
|
|
1924
|
+
source_property: i,
|
|
1925
|
+
store_property: properties[i].map.destination || i,
|
|
1836
1926
|
execute: function () {
|
|
1837
1927
|
var _execute = _asyncToGenerator(function* (data, old_data, ctx, type, args, mapping_dept, level, columns, serializationType) {
|
|
1838
1928
|
// Skip if columns are specified but this field isn't included
|
|
@@ -1841,7 +1931,7 @@ class SpiceModel {
|
|
|
1841
1931
|
}
|
|
1842
1932
|
return yield _this15.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], args, serializationType, mapping_dept, level, columns);
|
|
1843
1933
|
});
|
|
1844
|
-
function execute(
|
|
1934
|
+
function execute(_x4, _x5, _x6, _x7, _x8, _x9, _x0, _x1, _x10) {
|
|
1845
1935
|
return _execute.apply(this, arguments);
|
|
1846
1936
|
}
|
|
1847
1937
|
return execute;
|
|
@@ -1854,6 +1944,9 @@ class SpiceModel {
|
|
|
1854
1944
|
{
|
|
1855
1945
|
_this15.addModifier({
|
|
1856
1946
|
when: properties[i].map.when || "read",
|
|
1947
|
+
kind: "relationship_map",
|
|
1948
|
+
source_property: i,
|
|
1949
|
+
store_property: properties[i].map.destination || i,
|
|
1857
1950
|
execute: function () {
|
|
1858
1951
|
var _execute2 = _asyncToGenerator(function* (data, old_data, ctx, type, args, mapping_dept, level, columns, serializationType) {
|
|
1859
1952
|
// Skip if columns are specified but this field isn't included
|
|
@@ -1862,7 +1955,7 @@ class SpiceModel {
|
|
|
1862
1955
|
}
|
|
1863
1956
|
return yield _this15.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], args, serializationType, mapping_dept, level, columns);
|
|
1864
1957
|
});
|
|
1865
|
-
function execute(
|
|
1958
|
+
function execute(_x11, _x12, _x13, _x14, _x15, _x16, _x17, _x18, _x19) {
|
|
1866
1959
|
return _execute2.apply(this, arguments);
|
|
1867
1960
|
}
|
|
1868
1961
|
return execute;
|
|
@@ -1894,7 +1987,7 @@ class SpiceModel {
|
|
|
1894
1987
|
// Profiling: use track() for proper async context forking
|
|
1895
1988
|
var p = (_this16$_ctx = _this16[_ctx]) == null ? void 0 : _this16$_ctx.profiler;
|
|
1896
1989
|
var doSerialize = /*#__PURE__*/function () {
|
|
1897
|
-
var
|
|
1990
|
+
var _ref22 = _asyncToGenerator(function* () {
|
|
1898
1991
|
var _this16$_serializers, _this16$_serializers$;
|
|
1899
1992
|
// Early exit if serialization should not run.
|
|
1900
1993
|
if (!_this16.shouldSerializerRun(args, type)) {
|
|
@@ -1909,19 +2002,117 @@ class SpiceModel {
|
|
|
1909
2002
|
|
|
1910
2003
|
// Cache the modifiers lookup for the specified type.
|
|
1911
2004
|
var modifiers = ((_this16$_serializers = _this16[_serializers]) == null ? void 0 : (_this16$_serializers$ = _this16$_serializers[type]) == null ? void 0 : _this16$_serializers$.modifiers) || [];
|
|
1912
|
-
|
|
1913
|
-
var
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
2005
|
+
var invokeModifier = /*#__PURE__*/function () {
|
|
2006
|
+
var _ref23 = _asyncToGenerator(function* (modifier, index, inputData) {
|
|
2007
|
+
try {
|
|
2008
|
+
var result = yield modifier(inputData, old_data, _this16[_ctx], _this16.type, args, _this16[_mapping_dept], _this16[_level], _this16[_columns], type);
|
|
2009
|
+
if (result !== undefined) {
|
|
2010
|
+
return {
|
|
2011
|
+
hasValue: true,
|
|
2012
|
+
value: result
|
|
2013
|
+
};
|
|
2014
|
+
}
|
|
2015
|
+
console.warn("Modifier #" + index + " for type=" + _this16.type + " returned undefined, keeping previous data");
|
|
2016
|
+
} catch (error) {
|
|
2017
|
+
console.error("Modifier error in do_serialize (type=" + _this16.type + ", modifier #" + index + "):", error instanceof Error ? error.stack : "Non-Error thrown: " + JSON.stringify(error));
|
|
2018
|
+
}
|
|
2019
|
+
return {
|
|
2020
|
+
hasValue: false
|
|
2021
|
+
};
|
|
2022
|
+
});
|
|
2023
|
+
return function invokeModifier(_x20, _x21, _x22) {
|
|
2024
|
+
return _ref23.apply(this, arguments);
|
|
2025
|
+
};
|
|
2026
|
+
}();
|
|
2027
|
+
var modifierIndex = 0;
|
|
2028
|
+
var _loop2 = function* _loop2() {
|
|
2029
|
+
var _modifier$spiceModifi;
|
|
2030
|
+
var modifier = modifiers[modifierIndex];
|
|
2031
|
+
var canRunRelationshipMapsConcurrently = type === "read" && _this16[_level] === 0 && _this16[_mapping_concurrency] > 1 && (modifier == null ? void 0 : (_modifier$spiceModifi = modifier.spiceModifier) == null ? void 0 : _modifier$spiceModifi.kind) === "relationship_map";
|
|
2032
|
+
if (!canRunRelationshipMapsConcurrently) {
|
|
2033
|
+
var result = yield invokeModifier(modifier, modifierIndex, data);
|
|
2034
|
+
if (result.hasValue) data = result.value;
|
|
2035
|
+
modifierIndex += 1;
|
|
2036
|
+
return "continue";
|
|
2037
|
+
}
|
|
2038
|
+
var group = [];
|
|
2039
|
+
var destinations = new Set();
|
|
2040
|
+
while (modifierIndex < modifiers.length) {
|
|
2041
|
+
var candidate = modifiers[modifierIndex];
|
|
2042
|
+
var metadata = candidate == null ? void 0 : candidate.spiceModifier;
|
|
2043
|
+
if ((metadata == null ? void 0 : metadata.kind) !== "relationship_map") break;
|
|
2044
|
+
if (metadata.store_property && destinations.has(metadata.store_property)) {
|
|
2045
|
+
break;
|
|
2046
|
+
}
|
|
2047
|
+
if (metadata.store_property) {
|
|
2048
|
+
destinations.add(metadata.store_property);
|
|
1921
2049
|
}
|
|
1922
|
-
|
|
1923
|
-
|
|
2050
|
+
group.push({
|
|
2051
|
+
modifier: candidate,
|
|
2052
|
+
index: modifierIndex,
|
|
2053
|
+
metadata
|
|
2054
|
+
});
|
|
2055
|
+
modifierIndex += 1;
|
|
1924
2056
|
}
|
|
2057
|
+
var groupQueuedAt = process.hrtime.bigint();
|
|
2058
|
+
var activeRequestMappings = 0;
|
|
2059
|
+
var executeGroup = /*#__PURE__*/function () {
|
|
2060
|
+
var _ref24 = _asyncToGenerator(function* () {
|
|
2061
|
+
return yield runWithConcurrency(group, _this16[_mapping_concurrency], /*#__PURE__*/function () {
|
|
2062
|
+
var _ref25 = _asyncToGenerator(function* (entry) {
|
|
2063
|
+
var requestQueueWaitMs = Number(process.hrtime.bigint() - groupQueuedAt) / 1e6;
|
|
2064
|
+
var globalSlot = yield acquireGlobalMappingSlot();
|
|
2065
|
+
activeRequestMappings += 1;
|
|
2066
|
+
var profileMetadata = {
|
|
2067
|
+
source_property: entry.metadata.source_property,
|
|
2068
|
+
store_property: entry.metadata.store_property,
|
|
2069
|
+
request_queue_wait_ms: Math.round(requestQueueWaitMs * 100) / 100,
|
|
2070
|
+
global_queue_wait_ms: Math.round(globalSlot.waitMs * 100) / 100,
|
|
2071
|
+
active_request_mappings: activeRequestMappings,
|
|
2072
|
+
request_limit: _this16[_mapping_concurrency],
|
|
2073
|
+
active_global_mappings: globalSlot.active,
|
|
2074
|
+
global_limit: GLOBAL_MAPPING_CONCURRENCY
|
|
2075
|
+
};
|
|
2076
|
+
try {
|
|
2077
|
+
var execute = /*#__PURE__*/function () {
|
|
2078
|
+
var _ref26 = _asyncToGenerator(function* () {
|
|
2079
|
+
return yield invokeModifier(entry.modifier, entry.index, data);
|
|
2080
|
+
});
|
|
2081
|
+
return function execute() {
|
|
2082
|
+
return _ref26.apply(this, arguments);
|
|
2083
|
+
};
|
|
2084
|
+
}();
|
|
2085
|
+
if (p) {
|
|
2086
|
+
return yield p.track(_this16.type + ".map.concurrent." + entry.metadata.source_property, execute, profileMetadata);
|
|
2087
|
+
}
|
|
2088
|
+
return yield execute();
|
|
2089
|
+
} finally {
|
|
2090
|
+
activeRequestMappings -= 1;
|
|
2091
|
+
globalSlot.release();
|
|
2092
|
+
}
|
|
2093
|
+
});
|
|
2094
|
+
return function (_x23) {
|
|
2095
|
+
return _ref25.apply(this, arguments);
|
|
2096
|
+
};
|
|
2097
|
+
}());
|
|
2098
|
+
});
|
|
2099
|
+
return function executeGroup() {
|
|
2100
|
+
return _ref24.apply(this, arguments);
|
|
2101
|
+
};
|
|
2102
|
+
}();
|
|
2103
|
+
var groupMetadata = {
|
|
2104
|
+
map_count: group.length,
|
|
2105
|
+
request_limit: _this16[_mapping_concurrency],
|
|
2106
|
+
global_limit: GLOBAL_MAPPING_CONCURRENCY
|
|
2107
|
+
};
|
|
2108
|
+
var groupResults = p ? yield p.track(_this16.type + ".map.concurrent", executeGroup, groupMetadata) : yield executeGroup();
|
|
2109
|
+
for (var _result of groupResults) {
|
|
2110
|
+
if (_result == null ? void 0 : _result.hasValue) data = _result.value;
|
|
2111
|
+
}
|
|
2112
|
+
};
|
|
2113
|
+
while (modifierIndex < modifiers.length) {
|
|
2114
|
+
var _ret = yield* _loop2();
|
|
2115
|
+
if (_ret === "continue") continue;
|
|
1925
2116
|
}
|
|
1926
2117
|
|
|
1927
2118
|
// Ensure data is always an array for consistent processing.
|
|
@@ -1962,7 +2153,7 @@ class SpiceModel {
|
|
|
1962
2153
|
return originalIsArray ? data : data[0];
|
|
1963
2154
|
});
|
|
1964
2155
|
return function doSerialize() {
|
|
1965
|
-
return
|
|
2156
|
+
return _ref22.apply(this, arguments);
|
|
1966
2157
|
};
|
|
1967
2158
|
}();
|
|
1968
2159
|
try {
|
package/package.json
CHANGED
package/src/models/SpiceModel.js
CHANGED
|
@@ -24,9 +24,64 @@ var SDate = require("sonover-date"),
|
|
|
24
24
|
_mapping_dept = Symbol(),
|
|
25
25
|
_mapping_dept_scalar = Symbol(),
|
|
26
26
|
_mapping_dept_array = Symbol(),
|
|
27
|
+
_mapping_concurrency = Symbol(),
|
|
28
|
+
_skip_mapped_serialization = Symbol(),
|
|
27
29
|
_mapping_dept_exempt = Symbol(),
|
|
28
30
|
_current_path = Symbol();
|
|
29
31
|
|
|
32
|
+
const GLOBAL_MAPPING_CONCURRENCY = Math.max(
|
|
33
|
+
1,
|
|
34
|
+
Number(process.env.SPICE_MAPPING_GLOBAL_CONCURRENCY) || 32,
|
|
35
|
+
);
|
|
36
|
+
let activeGlobalMappings = 0;
|
|
37
|
+
const globalMappingWaiters = [];
|
|
38
|
+
|
|
39
|
+
function normalizeConcurrency(value, fallback = 1) {
|
|
40
|
+
const parsed = Number(value);
|
|
41
|
+
return Number.isFinite(parsed) && parsed > 0 ? Math.floor(parsed) : fallback;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async function acquireGlobalMappingSlot() {
|
|
45
|
+
const queuedAt = process.hrtime.bigint();
|
|
46
|
+
if (activeGlobalMappings < GLOBAL_MAPPING_CONCURRENCY) {
|
|
47
|
+
activeGlobalMappings += 1;
|
|
48
|
+
} else {
|
|
49
|
+
await new Promise((resolve) => globalMappingWaiters.push(resolve));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
active: activeGlobalMappings,
|
|
54
|
+
waitMs: Number(process.hrtime.bigint() - queuedAt) / 1e6,
|
|
55
|
+
release() {
|
|
56
|
+
const next = globalMappingWaiters.shift();
|
|
57
|
+
if (next) {
|
|
58
|
+
next();
|
|
59
|
+
} else {
|
|
60
|
+
activeGlobalMappings -= 1;
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async function runWithConcurrency(items, concurrency, worker) {
|
|
67
|
+
const results = new Array(items.length);
|
|
68
|
+
let nextIndex = 0;
|
|
69
|
+
|
|
70
|
+
async function runWorker() {
|
|
71
|
+
while (nextIndex < items.length) {
|
|
72
|
+
const index = nextIndex;
|
|
73
|
+
nextIndex += 1;
|
|
74
|
+
results[index] = await worker(items[index], index);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const workerCount = Math.min(concurrency, items.length);
|
|
79
|
+
await Promise.all(
|
|
80
|
+
Array.from({ length: workerCount }, () => runWorker()),
|
|
81
|
+
);
|
|
82
|
+
return results;
|
|
83
|
+
}
|
|
84
|
+
|
|
30
85
|
//const _type = Symbol("type");
|
|
31
86
|
|
|
32
87
|
let that;
|
|
@@ -71,6 +126,13 @@ export default class SpiceModel {
|
|
|
71
126
|
legacyMappingDept ??
|
|
72
127
|
requestProfile.mapping_dept_array ??
|
|
73
128
|
defaultMappingDept;
|
|
129
|
+
this[_mapping_concurrency] = normalizeConcurrency(
|
|
130
|
+
modelArgs.mapping_concurrency ?? requestProfile.mapping_concurrency,
|
|
131
|
+
);
|
|
132
|
+
this[_skip_mapped_serialization] =
|
|
133
|
+
modelArgs.skip_mapped_serialization ??
|
|
134
|
+
requestProfile.skip_mapped_serialization ??
|
|
135
|
+
false;
|
|
74
136
|
this[_mapping_dept_exempt] = modelArgs.mapping_dept_exempt || [];
|
|
75
137
|
this.type = "";
|
|
76
138
|
this.collection = args.connection;
|
|
@@ -277,6 +339,15 @@ export default class SpiceModel {
|
|
|
277
339
|
) {
|
|
278
340
|
this[_mapping_dept_array] = args.mapping_dept_array;
|
|
279
341
|
}
|
|
342
|
+
if (args.skip_mapped_serialization !== undefined) {
|
|
343
|
+
this[_skip_mapped_serialization] = args.skip_mapped_serialization;
|
|
344
|
+
}
|
|
345
|
+
if (args.mapping_concurrency !== undefined) {
|
|
346
|
+
this[_mapping_concurrency] = normalizeConcurrency(
|
|
347
|
+
args.mapping_concurrency,
|
|
348
|
+
this[_mapping_concurrency],
|
|
349
|
+
);
|
|
350
|
+
}
|
|
280
351
|
if (args.mapping_dept_exempt) {
|
|
281
352
|
this[_mapping_dept_exempt] = args.mapping_dept_exempt;
|
|
282
353
|
}
|
|
@@ -1815,6 +1886,8 @@ export default class SpiceModel {
|
|
|
1815
1886
|
mapping_dept: this[_mapping_dept],
|
|
1816
1887
|
mapping_dept_scalar: this[_mapping_dept_scalar],
|
|
1817
1888
|
mapping_dept_array: this[_mapping_dept_array],
|
|
1889
|
+
mapping_concurrency: this[_mapping_concurrency],
|
|
1890
|
+
skip_mapped_serialization: this[_skip_mapped_serialization],
|
|
1818
1891
|
mapping_dept_exempt: this[_mapping_dept_exempt],
|
|
1819
1892
|
_columns: columnArray.join(","),
|
|
1820
1893
|
_current_path: childPath,
|
|
@@ -1824,6 +1897,7 @@ export default class SpiceModel {
|
|
|
1824
1897
|
skip_hooks: true,
|
|
1825
1898
|
ids: ids,
|
|
1826
1899
|
columns: columnArray,
|
|
1900
|
+
skip_read_serialize: this[_skip_mapped_serialization],
|
|
1827
1901
|
});
|
|
1828
1902
|
}),
|
|
1829
1903
|
);
|
|
@@ -1990,6 +2064,8 @@ export default class SpiceModel {
|
|
|
1990
2064
|
mapping_dept: this[_mapping_dept],
|
|
1991
2065
|
mapping_dept_scalar: this[_mapping_dept_scalar],
|
|
1992
2066
|
mapping_dept_array: this[_mapping_dept_array],
|
|
2067
|
+
mapping_concurrency: this[_mapping_concurrency],
|
|
2068
|
+
skip_mapped_serialization: this[_skip_mapped_serialization],
|
|
1993
2069
|
mapping_dept_exempt: this[_mapping_dept_exempt],
|
|
1994
2070
|
_columns: columnArray.join(","),
|
|
1995
2071
|
_current_path: childPath,
|
|
@@ -1997,6 +2073,7 @@ export default class SpiceModel {
|
|
|
1997
2073
|
skip_hooks: true,
|
|
1998
2074
|
ids: ids,
|
|
1999
2075
|
columns: columnArray,
|
|
2076
|
+
skip_read_serialize: this[_skip_mapped_serialization],
|
|
2000
2077
|
});
|
|
2001
2078
|
}),
|
|
2002
2079
|
);
|
|
@@ -2070,8 +2147,21 @@ export default class SpiceModel {
|
|
|
2070
2147
|
return original_is_array ? data : data[0];
|
|
2071
2148
|
}
|
|
2072
2149
|
|
|
2073
|
-
addModifier({
|
|
2150
|
+
addModifier({
|
|
2151
|
+
when,
|
|
2152
|
+
execute,
|
|
2153
|
+
kind,
|
|
2154
|
+
source_property,
|
|
2155
|
+
store_property,
|
|
2156
|
+
}) {
|
|
2074
2157
|
if (this[_serializers][when]) {
|
|
2158
|
+
if (kind) {
|
|
2159
|
+
execute.spiceModifier = {
|
|
2160
|
+
kind,
|
|
2161
|
+
source_property,
|
|
2162
|
+
store_property,
|
|
2163
|
+
};
|
|
2164
|
+
}
|
|
2075
2165
|
this[_serializers][when]["modifiers"].push(execute);
|
|
2076
2166
|
}
|
|
2077
2167
|
}
|
|
@@ -2145,6 +2235,9 @@ export default class SpiceModel {
|
|
|
2145
2235
|
case "string": {
|
|
2146
2236
|
this.addModifier({
|
|
2147
2237
|
when: properties[i].map.when || "read",
|
|
2238
|
+
kind: "relationship_map",
|
|
2239
|
+
source_property: i,
|
|
2240
|
+
store_property: properties[i].map.destination || i,
|
|
2148
2241
|
execute: async (
|
|
2149
2242
|
data,
|
|
2150
2243
|
old_data,
|
|
@@ -2182,6 +2275,9 @@ export default class SpiceModel {
|
|
|
2182
2275
|
case "array": {
|
|
2183
2276
|
this.addModifier({
|
|
2184
2277
|
when: properties[i].map.when || "read",
|
|
2278
|
+
kind: "relationship_map",
|
|
2279
|
+
source_property: i,
|
|
2280
|
+
store_property: properties[i].map.destination || i,
|
|
2185
2281
|
execute: async (
|
|
2186
2282
|
data,
|
|
2187
2283
|
old_data,
|
|
@@ -2245,11 +2341,10 @@ export default class SpiceModel {
|
|
|
2245
2341
|
|
|
2246
2342
|
// Cache the modifiers lookup for the specified type.
|
|
2247
2343
|
const modifiers = this[_serializers]?.[type]?.modifiers || [];
|
|
2248
|
-
|
|
2249
|
-
const modifier = modifiers[i];
|
|
2344
|
+
const invokeModifier = async (modifier, index, inputData) => {
|
|
2250
2345
|
try {
|
|
2251
2346
|
const result = await modifier(
|
|
2252
|
-
|
|
2347
|
+
inputData,
|
|
2253
2348
|
old_data,
|
|
2254
2349
|
this[_ctx],
|
|
2255
2350
|
this.type,
|
|
@@ -2259,22 +2354,117 @@ export default class SpiceModel {
|
|
|
2259
2354
|
this[_columns],
|
|
2260
2355
|
type,
|
|
2261
2356
|
);
|
|
2262
|
-
// Guard against modifiers that return undefined
|
|
2263
2357
|
if (result !== undefined) {
|
|
2264
|
-
|
|
2265
|
-
} else {
|
|
2266
|
-
console.warn(
|
|
2267
|
-
`Modifier #${i} for type=${this.type} returned undefined, keeping previous data`,
|
|
2268
|
-
);
|
|
2358
|
+
return { hasValue: true, value: result };
|
|
2269
2359
|
}
|
|
2360
|
+
console.warn(
|
|
2361
|
+
`Modifier #${index} for type=${this.type} returned undefined, keeping previous data`,
|
|
2362
|
+
);
|
|
2270
2363
|
} catch (error) {
|
|
2271
2364
|
console.error(
|
|
2272
|
-
`Modifier error in do_serialize (type=${this.type}, modifier #${
|
|
2365
|
+
`Modifier error in do_serialize (type=${this.type}, modifier #${index}):`,
|
|
2273
2366
|
error instanceof Error ?
|
|
2274
2367
|
error.stack
|
|
2275
2368
|
: `Non-Error thrown: ${JSON.stringify(error)}`,
|
|
2276
2369
|
);
|
|
2277
2370
|
}
|
|
2371
|
+
return { hasValue: false };
|
|
2372
|
+
};
|
|
2373
|
+
|
|
2374
|
+
let modifierIndex = 0;
|
|
2375
|
+
while (modifierIndex < modifiers.length) {
|
|
2376
|
+
const modifier = modifiers[modifierIndex];
|
|
2377
|
+
const canRunRelationshipMapsConcurrently =
|
|
2378
|
+
type === "read" &&
|
|
2379
|
+
this[_level] === 0 &&
|
|
2380
|
+
this[_mapping_concurrency] > 1 &&
|
|
2381
|
+
modifier?.spiceModifier?.kind === "relationship_map";
|
|
2382
|
+
|
|
2383
|
+
if (!canRunRelationshipMapsConcurrently) {
|
|
2384
|
+
const result = await invokeModifier(modifier, modifierIndex, data);
|
|
2385
|
+
if (result.hasValue) data = result.value;
|
|
2386
|
+
modifierIndex += 1;
|
|
2387
|
+
continue;
|
|
2388
|
+
}
|
|
2389
|
+
|
|
2390
|
+
const group = [];
|
|
2391
|
+
const destinations = new Set();
|
|
2392
|
+
while (modifierIndex < modifiers.length) {
|
|
2393
|
+
const candidate = modifiers[modifierIndex];
|
|
2394
|
+
const metadata = candidate?.spiceModifier;
|
|
2395
|
+
if (metadata?.kind !== "relationship_map") break;
|
|
2396
|
+
if (
|
|
2397
|
+
metadata.store_property &&
|
|
2398
|
+
destinations.has(metadata.store_property)
|
|
2399
|
+
) {
|
|
2400
|
+
break;
|
|
2401
|
+
}
|
|
2402
|
+
if (metadata.store_property) {
|
|
2403
|
+
destinations.add(metadata.store_property);
|
|
2404
|
+
}
|
|
2405
|
+
group.push({ modifier: candidate, index: modifierIndex, metadata });
|
|
2406
|
+
modifierIndex += 1;
|
|
2407
|
+
}
|
|
2408
|
+
|
|
2409
|
+
const groupQueuedAt = process.hrtime.bigint();
|
|
2410
|
+
let activeRequestMappings = 0;
|
|
2411
|
+
const executeGroup = async () =>
|
|
2412
|
+
await runWithConcurrency(
|
|
2413
|
+
group,
|
|
2414
|
+
this[_mapping_concurrency],
|
|
2415
|
+
async (entry) => {
|
|
2416
|
+
const requestQueueWaitMs =
|
|
2417
|
+
Number(process.hrtime.bigint() - groupQueuedAt) / 1e6;
|
|
2418
|
+
const globalSlot = await acquireGlobalMappingSlot();
|
|
2419
|
+
activeRequestMappings += 1;
|
|
2420
|
+
const profileMetadata = {
|
|
2421
|
+
source_property: entry.metadata.source_property,
|
|
2422
|
+
store_property: entry.metadata.store_property,
|
|
2423
|
+
request_queue_wait_ms:
|
|
2424
|
+
Math.round(requestQueueWaitMs * 100) / 100,
|
|
2425
|
+
global_queue_wait_ms:
|
|
2426
|
+
Math.round(globalSlot.waitMs * 100) / 100,
|
|
2427
|
+
active_request_mappings: activeRequestMappings,
|
|
2428
|
+
request_limit: this[_mapping_concurrency],
|
|
2429
|
+
active_global_mappings: globalSlot.active,
|
|
2430
|
+
global_limit: GLOBAL_MAPPING_CONCURRENCY,
|
|
2431
|
+
};
|
|
2432
|
+
|
|
2433
|
+
try {
|
|
2434
|
+
const execute = async () =>
|
|
2435
|
+
await invokeModifier(entry.modifier, entry.index, data);
|
|
2436
|
+
if (p) {
|
|
2437
|
+
return await p.track(
|
|
2438
|
+
`${this.type}.map.concurrent.${entry.metadata.source_property}`,
|
|
2439
|
+
execute,
|
|
2440
|
+
profileMetadata,
|
|
2441
|
+
);
|
|
2442
|
+
}
|
|
2443
|
+
return await execute();
|
|
2444
|
+
} finally {
|
|
2445
|
+
activeRequestMappings -= 1;
|
|
2446
|
+
globalSlot.release();
|
|
2447
|
+
}
|
|
2448
|
+
},
|
|
2449
|
+
);
|
|
2450
|
+
|
|
2451
|
+
const groupMetadata = {
|
|
2452
|
+
map_count: group.length,
|
|
2453
|
+
request_limit: this[_mapping_concurrency],
|
|
2454
|
+
global_limit: GLOBAL_MAPPING_CONCURRENCY,
|
|
2455
|
+
};
|
|
2456
|
+
const groupResults =
|
|
2457
|
+
p ?
|
|
2458
|
+
await p.track(
|
|
2459
|
+
`${this.type}.map.concurrent`,
|
|
2460
|
+
executeGroup,
|
|
2461
|
+
groupMetadata,
|
|
2462
|
+
)
|
|
2463
|
+
: await executeGroup();
|
|
2464
|
+
|
|
2465
|
+
for (const result of groupResults) {
|
|
2466
|
+
if (result?.hasValue) data = result.value;
|
|
2467
|
+
}
|
|
2278
2468
|
}
|
|
2279
2469
|
|
|
2280
2470
|
// Ensure data is always an array for consistent processing.
|
|
@@ -7,7 +7,9 @@ describe('SpiceModel type-specific mapping depth', () => {
|
|
|
7
7
|
capture.constructorArgs.push(args);
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
async getMulti(
|
|
10
|
+
async getMulti(args) {
|
|
11
|
+
const { ids } = args;
|
|
12
|
+
capture.getMultiArgs.push(args);
|
|
11
13
|
capture.getMultiIds.push(ids);
|
|
12
14
|
return records.filter((record) => ids.includes(record.id));
|
|
13
15
|
}
|
|
@@ -15,8 +17,16 @@ describe('SpiceModel type-specific mapping depth', () => {
|
|
|
15
17
|
}
|
|
16
18
|
|
|
17
19
|
function createMappedModel(args = {}) {
|
|
18
|
-
const scalarCapture = {
|
|
19
|
-
|
|
20
|
+
const scalarCapture = {
|
|
21
|
+
constructorArgs: [],
|
|
22
|
+
getMultiArgs: [],
|
|
23
|
+
getMultiIds: [],
|
|
24
|
+
};
|
|
25
|
+
const arrayCapture = {
|
|
26
|
+
constructorArgs: [],
|
|
27
|
+
getMultiArgs: [],
|
|
28
|
+
getMultiIds: [],
|
|
29
|
+
};
|
|
20
30
|
const ScalarModel = createRelatedModel(
|
|
21
31
|
[{ id: 'group-1', name: 'Administrators' }],
|
|
22
32
|
scalarCapture
|
|
@@ -51,6 +61,41 @@ describe('SpiceModel type-specific mapping depth', () => {
|
|
|
51
61
|
return { model, scalarCapture, arrayCapture };
|
|
52
62
|
}
|
|
53
63
|
|
|
64
|
+
function createConcurrencyModel(mappingConcurrency, extraArgs = {}) {
|
|
65
|
+
const stats = { active: 0, maxActive: 0 };
|
|
66
|
+
class RelatedModel {
|
|
67
|
+
async getMulti({ ids }) {
|
|
68
|
+
stats.active += 1;
|
|
69
|
+
stats.maxActive = Math.max(stats.maxActive, stats.active);
|
|
70
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
71
|
+
stats.active -= 1;
|
|
72
|
+
return ids.map((id) => ({ id, name: id }));
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const props = {};
|
|
77
|
+
const record = { id: 'parent-concurrent', type: 'parent' };
|
|
78
|
+
for (let index = 1; index <= 3; index += 1) {
|
|
79
|
+
const field = `relationship_${index}`;
|
|
80
|
+
props[field] = {
|
|
81
|
+
type: 'string',
|
|
82
|
+
map: { type: 'model', reference: RelatedModel }
|
|
83
|
+
};
|
|
84
|
+
record[field] = `related-${index}`;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const model = createTestModel({
|
|
88
|
+
type: 'parent',
|
|
89
|
+
args: {
|
|
90
|
+
...extraArgs,
|
|
91
|
+
...(mappingConcurrency ? { mapping_concurrency: mappingConcurrency } : {})
|
|
92
|
+
},
|
|
93
|
+
props
|
|
94
|
+
});
|
|
95
|
+
seedDatabase(model, record);
|
|
96
|
+
return { model, stats };
|
|
97
|
+
}
|
|
98
|
+
|
|
54
99
|
test('uses scalar depth 1 and array depth 3 from the request profile', async () => {
|
|
55
100
|
const ctx = {
|
|
56
101
|
state: {
|
|
@@ -127,7 +172,8 @@ describe('SpiceModel type-specific mapping depth', () => {
|
|
|
127
172
|
test('propagates both depths to related model instances', async () => {
|
|
128
173
|
const { model, arrayCapture } = createMappedModel({
|
|
129
174
|
mapping_dept_scalar: 1,
|
|
130
|
-
mapping_dept_array: 3
|
|
175
|
+
mapping_dept_array: 3,
|
|
176
|
+
mapping_concurrency: 6
|
|
131
177
|
});
|
|
132
178
|
|
|
133
179
|
await model.getMulti({ ids: ['parent-1'] });
|
|
@@ -136,13 +182,105 @@ describe('SpiceModel type-specific mapping depth', () => {
|
|
|
136
182
|
expect.objectContaining({
|
|
137
183
|
mapping_dept_scalar: 1,
|
|
138
184
|
mapping_dept_array: 3,
|
|
185
|
+
mapping_concurrency: 6,
|
|
139
186
|
_level: 1
|
|
140
187
|
})
|
|
141
188
|
);
|
|
142
189
|
});
|
|
143
190
|
|
|
191
|
+
test('runs independent relationship maps with bounded concurrency', async () => {
|
|
192
|
+
const { model, stats } = createConcurrencyModel(2);
|
|
193
|
+
|
|
194
|
+
const result = await model.get({ id: 'parent-concurrent' });
|
|
195
|
+
|
|
196
|
+
expect(stats.maxActive).toBe(2);
|
|
197
|
+
expect(result.relationship_1).toEqual(
|
|
198
|
+
expect.objectContaining({ id: 'related-1' })
|
|
199
|
+
);
|
|
200
|
+
expect(result.relationship_3).toEqual(
|
|
201
|
+
expect.objectContaining({ id: 'related-3' })
|
|
202
|
+
);
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
test('keeps relationship maps sequential when concurrency is not enabled', async () => {
|
|
206
|
+
const { model, stats } = createConcurrencyModel();
|
|
207
|
+
|
|
208
|
+
await model.get({ id: 'parent-concurrent' });
|
|
209
|
+
|
|
210
|
+
expect(stats.maxActive).toBe(1);
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
test('keeps nested relationship maps sequential to avoid recursive slot use', async () => {
|
|
214
|
+
const { model, stats } = createConcurrencyModel(2, { _level: 1 });
|
|
215
|
+
|
|
216
|
+
await model.get({ id: 'parent-concurrent' });
|
|
217
|
+
|
|
218
|
+
expect(stats.maxActive).toBe(1);
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
test('profiles concurrent groups and mapping slot pressure', async () => {
|
|
222
|
+
const calls = [];
|
|
223
|
+
const profiler = {
|
|
224
|
+
track: jest.fn(async (name, execute, metadata) => {
|
|
225
|
+
calls.push({ name, metadata });
|
|
226
|
+
return await execute();
|
|
227
|
+
})
|
|
228
|
+
};
|
|
229
|
+
const { model } = createConcurrencyModel(2, { ctx: { profiler } });
|
|
230
|
+
|
|
231
|
+
await model.get({ id: 'parent-concurrent' });
|
|
232
|
+
|
|
233
|
+
expect(calls).toEqual(
|
|
234
|
+
expect.arrayContaining([
|
|
235
|
+
expect.objectContaining({
|
|
236
|
+
name: 'parent.map.concurrent',
|
|
237
|
+
metadata: expect.objectContaining({
|
|
238
|
+
map_count: 3,
|
|
239
|
+
request_limit: 2
|
|
240
|
+
})
|
|
241
|
+
}),
|
|
242
|
+
expect.objectContaining({
|
|
243
|
+
name: 'parent.map.concurrent.relationship_1',
|
|
244
|
+
metadata: expect.objectContaining({
|
|
245
|
+
request_limit: 2,
|
|
246
|
+
active_request_mappings: expect.any(Number),
|
|
247
|
+
active_global_mappings: expect.any(Number),
|
|
248
|
+
request_queue_wait_ms: expect.any(Number),
|
|
249
|
+
global_queue_wait_ms: expect.any(Number)
|
|
250
|
+
})
|
|
251
|
+
})
|
|
252
|
+
])
|
|
253
|
+
);
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
test('skips serialization for mapped child reads when requested by the profile', async () => {
|
|
257
|
+
const ctx = {
|
|
258
|
+
state: {
|
|
259
|
+
read_profile: {
|
|
260
|
+
mapping_dept_scalar: 2,
|
|
261
|
+
mapping_dept_array: 3,
|
|
262
|
+
skip_mapped_serialization: true
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
};
|
|
266
|
+
const { model, scalarCapture, arrayCapture } = createMappedModel({ ctx });
|
|
267
|
+
|
|
268
|
+
const result = await model.get({ id: 'parent-1' });
|
|
269
|
+
|
|
270
|
+
expect(result.group).toEqual(
|
|
271
|
+
expect.objectContaining({ id: 'group-1', name: 'Administrators' })
|
|
272
|
+
);
|
|
273
|
+
expect(result.children).toHaveLength(2);
|
|
274
|
+
expect(scalarCapture.getMultiArgs[0]).toEqual(
|
|
275
|
+
expect.objectContaining({ skip_hooks: true, skip_read_serialize: true })
|
|
276
|
+
);
|
|
277
|
+
expect(arrayCapture.getMultiArgs[0]).toEqual(
|
|
278
|
+
expect.objectContaining({ skip_hooks: true, skip_read_serialize: true })
|
|
279
|
+
);
|
|
280
|
+
});
|
|
281
|
+
|
|
144
282
|
test('does not apply the request read profile to write serialization', async () => {
|
|
145
|
-
const capture = { constructorArgs: [], getMultiIds: [] };
|
|
283
|
+
const capture = { constructorArgs: [], getMultiArgs: [], getMultiIds: [] };
|
|
146
284
|
const RelatedModel = createRelatedModel(
|
|
147
285
|
[{ id: 'group-1', name: 'Administrators' }],
|
|
148
286
|
capture
|