spice-js 2.7.28 → 2.7.30
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/index.js +3 -1
- package/build/models/SpiceModel.js +101 -65
- package/package.json +1 -1
- package/src/index.js +3 -1
- package/src/models/SpiceModel.js +82 -16
- package/tests/__mocks__/globalMocks.js +3 -1
- package/tests/fixtures/testData.js +3 -1
- package/tests/helpers/modelFactory.js +7 -1
- package/tests/jest-env.js +3 -1
- package/tests/models/SpiceModel.mapping-depth.test.js +182 -0
package/build/index.js
CHANGED
|
@@ -53,7 +53,9 @@ function _asyncToGenerator(n) { return function () { var t = this, e = arguments
|
|
|
53
53
|
var path = require("path");
|
|
54
54
|
//import IO from "koa-socket-2";
|
|
55
55
|
var cors = require("kcors");
|
|
56
|
-
|
|
56
|
+
// Preserve an existing host/test configuration when the package is imported.
|
|
57
|
+
// Production boot still starts from an empty object because spice is undefined.
|
|
58
|
+
global.spice = global.spice || {};
|
|
57
59
|
require("dotenv").config({
|
|
58
60
|
path: path.resolve("./")
|
|
59
61
|
});
|
|
@@ -27,6 +27,8 @@ var SDate = require("sonover-date"),
|
|
|
27
27
|
_serializers = Symbol(),
|
|
28
28
|
_level = Symbol(),
|
|
29
29
|
_mapping_dept = Symbol(),
|
|
30
|
+
_mapping_dept_scalar = Symbol(),
|
|
31
|
+
_mapping_dept_array = Symbol(),
|
|
30
32
|
_mapping_dept_exempt = Symbol(),
|
|
31
33
|
_current_path = Symbol();
|
|
32
34
|
|
|
@@ -48,21 +50,27 @@ class SpiceModel {
|
|
|
48
50
|
args = {};
|
|
49
51
|
}
|
|
50
52
|
try {
|
|
51
|
-
var _args2,
|
|
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;
|
|
54
|
+
var modelArgs = ((_args2 = args) == null ? void 0 : _args2.args) || {};
|
|
55
|
+
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
|
+
var defaultMappingDept = process.env.MAPPING_DEPT || 3;
|
|
57
|
+
var legacyMappingDept = modelArgs.mapping_dept !== undefined && modelArgs.mapping_dept !== null ? modelArgs.mapping_dept : undefined;
|
|
52
58
|
var dbtype = spice.config.database.connections[args.connection].type || "couchbase";
|
|
53
59
|
var Database = require("spice-" + dbtype);
|
|
54
|
-
this[_mapping_dept] =
|
|
55
|
-
this[
|
|
60
|
+
this[_mapping_dept] = legacyMappingDept != null ? legacyMappingDept : defaultMappingDept;
|
|
61
|
+
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
|
+
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;
|
|
63
|
+
this[_mapping_dept_exempt] = modelArgs.mapping_dept_exempt || [];
|
|
56
64
|
this.type = "";
|
|
57
65
|
this.collection = args.connection;
|
|
58
66
|
this[_args] = args.args;
|
|
59
67
|
this[_external_modifier_loaded] = false;
|
|
60
68
|
this[_disable_lifecycle_events] = ((_args$args = args.args) == null ? void 0 : _args$args.disable_lifecycle_events) || false;
|
|
61
|
-
this[_ctx] = (
|
|
62
|
-
this[_skip_cache] = ((
|
|
63
|
-
this[_level] = ((
|
|
64
|
-
this[_current_path] = ((
|
|
65
|
-
this[_columns] = ((
|
|
69
|
+
this[_ctx] = (_args3 = args) == null ? void 0 : (_args3$args = _args3.args) == null ? void 0 : _args3$args.ctx;
|
|
70
|
+
this[_skip_cache] = ((_args4 = args) == null ? void 0 : (_args4$args = _args4.args) == null ? void 0 : _args4$args.skip_cache) || false;
|
|
71
|
+
this[_level] = ((_args5 = args) == null ? void 0 : (_args5$args = _args5.args) == null ? void 0 : _args5$args._level) || 0;
|
|
72
|
+
this[_current_path] = ((_args6 = args) == null ? void 0 : (_args6$args = _args6.args) == null ? void 0 : _args6$args._current_path) || "";
|
|
73
|
+
this[_columns] = ((_args7 = args) == null ? void 0 : (_args7$args = _args7.args) == null ? void 0 : _args7$args._columns) || "";
|
|
66
74
|
this[_hooks] = {
|
|
67
75
|
create: {
|
|
68
76
|
before: [],
|
|
@@ -209,6 +217,26 @@ class SpiceModel {
|
|
|
209
217
|
}
|
|
210
218
|
// }
|
|
211
219
|
}
|
|
220
|
+
applyMappingDepthArgs(args) {
|
|
221
|
+
if (args === void 0) {
|
|
222
|
+
args = {};
|
|
223
|
+
}
|
|
224
|
+
var hasLegacyDepth = args.mapping_dept !== undefined && args.mapping_dept !== null;
|
|
225
|
+
if (hasLegacyDepth) {
|
|
226
|
+
this[_mapping_dept] = args.mapping_dept;
|
|
227
|
+
this[_mapping_dept_scalar] = args.mapping_dept;
|
|
228
|
+
this[_mapping_dept_array] = args.mapping_dept;
|
|
229
|
+
}
|
|
230
|
+
if (args.mapping_dept_scalar !== undefined && args.mapping_dept_scalar !== null) {
|
|
231
|
+
this[_mapping_dept_scalar] = args.mapping_dept_scalar;
|
|
232
|
+
}
|
|
233
|
+
if (args.mapping_dept_array !== undefined && args.mapping_dept_array !== null) {
|
|
234
|
+
this[_mapping_dept_array] = args.mapping_dept_array;
|
|
235
|
+
}
|
|
236
|
+
if (args.mapping_dept_exempt) {
|
|
237
|
+
this[_mapping_dept_exempt] = args.mapping_dept_exempt;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
212
240
|
get database() {
|
|
213
241
|
return this[_database];
|
|
214
242
|
}
|
|
@@ -250,13 +278,13 @@ class SpiceModel {
|
|
|
250
278
|
return [];
|
|
251
279
|
})();
|
|
252
280
|
}
|
|
253
|
-
createValidationString(
|
|
281
|
+
createValidationString(_ref5) {
|
|
254
282
|
var {
|
|
255
283
|
property_name,
|
|
256
284
|
props,
|
|
257
285
|
operation,
|
|
258
286
|
complete
|
|
259
|
-
} =
|
|
287
|
+
} = _ref5;
|
|
260
288
|
var operationExempt = {
|
|
261
289
|
PUT: ["required", "unique"]
|
|
262
290
|
};
|
|
@@ -487,14 +515,13 @@ class SpiceModel {
|
|
|
487
515
|
// Profiling: use track() for proper async context forking
|
|
488
516
|
var p = (_this4$_ctx = _this4[_ctx]) == null ? void 0 : _this4$_ctx.profiler;
|
|
489
517
|
var doGet = /*#__PURE__*/function () {
|
|
490
|
-
var
|
|
491
|
-
if (args.mapping_dept) _this4[_mapping_dept] = args.mapping_dept;
|
|
492
|
-
if (args.mapping_dept_exempt) _this4[_mapping_dept_exempt] = args.mapping_dept_exempt;
|
|
518
|
+
var _ref6 = _asyncToGenerator(function* () {
|
|
493
519
|
if (!args) {
|
|
494
520
|
args = {};
|
|
495
521
|
}
|
|
522
|
+
_this4.applyMappingDepthArgs(args);
|
|
496
523
|
if (_.isString(args.id)) {
|
|
497
|
-
var
|
|
524
|
+
var _args8, _results, _results2, _results3, _results4, _results5, _results6;
|
|
498
525
|
if (args.skip_hooks !== true) {
|
|
499
526
|
yield _this4.run_hook(args, "get", "before");
|
|
500
527
|
}
|
|
@@ -514,7 +541,7 @@ class SpiceModel {
|
|
|
514
541
|
var originalColumns = args.columns;
|
|
515
542
|
|
|
516
543
|
// Extract nestings from columns if specified
|
|
517
|
-
var nestings = _this4.extractNestings(((
|
|
544
|
+
var nestings = _this4.extractNestings(((_args8 = args) == null ? void 0 : _args8.columns) || "", _this4.type);
|
|
518
545
|
|
|
519
546
|
// Build join metadata and prepare columns
|
|
520
547
|
_this4.buildJoinMetadata(nestings, args);
|
|
@@ -597,14 +624,14 @@ class SpiceModel {
|
|
|
597
624
|
}
|
|
598
625
|
});
|
|
599
626
|
return function doGet() {
|
|
600
|
-
return
|
|
627
|
+
return _ref6.apply(this, arguments);
|
|
601
628
|
};
|
|
602
629
|
}();
|
|
603
630
|
try {
|
|
604
631
|
if (p) {
|
|
605
|
-
var
|
|
632
|
+
var _args9;
|
|
606
633
|
return yield p.track(_this4.type + ".get", doGet, {
|
|
607
|
-
id: (
|
|
634
|
+
id: (_args9 = args) == null ? void 0 : _args9.id
|
|
608
635
|
});
|
|
609
636
|
}
|
|
610
637
|
return yield doGet();
|
|
@@ -632,14 +659,15 @@ class SpiceModel {
|
|
|
632
659
|
// ⚡ Profiling: use track() for proper async context forking
|
|
633
660
|
var p = (_this6$_ctx = _this6[_ctx]) == null ? void 0 : _this6$_ctx.profiler;
|
|
634
661
|
var doGetMulti = /*#__PURE__*/function () {
|
|
635
|
-
var
|
|
662
|
+
var _ref9 = _asyncToGenerator(function* () {
|
|
636
663
|
if (!args) {
|
|
637
664
|
args = {};
|
|
638
665
|
}
|
|
666
|
+
_this6.applyMappingDepthArgs(args);
|
|
639
667
|
if (args.skip_hooks != true) {
|
|
640
668
|
yield _this6.run_hook(_this6, "list", "before");
|
|
641
669
|
}
|
|
642
|
-
_.
|
|
670
|
+
args.ids = _.uniq(_.filter(args.ids || [], id => _.isString(id) && id.length > 0));
|
|
643
671
|
var key = "multi-get::" + _this6.type + "::" + _.join(args.ids, "|") + ":" + args.columns;
|
|
644
672
|
var results = [];
|
|
645
673
|
if (args.ids.length > 0) {
|
|
@@ -673,14 +701,14 @@ class SpiceModel {
|
|
|
673
701
|
return results;
|
|
674
702
|
});
|
|
675
703
|
return function doGetMulti() {
|
|
676
|
-
return
|
|
704
|
+
return _ref9.apply(this, arguments);
|
|
677
705
|
};
|
|
678
706
|
}();
|
|
679
707
|
try {
|
|
680
708
|
if (p) {
|
|
681
|
-
var
|
|
709
|
+
var _args0, _args0$ids;
|
|
682
710
|
return yield p.track(_this6.type + ".getMulti", doGetMulti, {
|
|
683
|
-
ids_count: ((
|
|
711
|
+
ids_count: ((_args0 = args) == null ? void 0 : (_args0$ids = _args0.ids) == null ? void 0 : _args0$ids.length) || 0
|
|
684
712
|
});
|
|
685
713
|
}
|
|
686
714
|
return yield doGetMulti();
|
|
@@ -729,7 +757,7 @@ class SpiceModel {
|
|
|
729
757
|
// Profiling: use track() for proper async context forking
|
|
730
758
|
var p = (_this8$_ctx = _this8[_ctx]) == null ? void 0 : _this8$_ctx.profiler;
|
|
731
759
|
var doUpdate = /*#__PURE__*/function () {
|
|
732
|
-
var
|
|
760
|
+
var _ref0 = _asyncToGenerator(function* () {
|
|
733
761
|
_this8.updated_at = new SDate().now();
|
|
734
762
|
var results = yield _this8.database.get(args.id);
|
|
735
763
|
var item_exist = yield _this8.exist(results);
|
|
@@ -773,7 +801,7 @@ class SpiceModel {
|
|
|
773
801
|
});
|
|
774
802
|
});
|
|
775
803
|
return function doUpdate() {
|
|
776
|
-
return
|
|
804
|
+
return _ref0.apply(this, arguments);
|
|
777
805
|
};
|
|
778
806
|
}();
|
|
779
807
|
try {
|
|
@@ -799,7 +827,7 @@ class SpiceModel {
|
|
|
799
827
|
// Profiling: use track() for proper async context forking
|
|
800
828
|
var p = (_this9$_ctx = _this9[_ctx]) == null ? void 0 : _this9$_ctx.profiler;
|
|
801
829
|
var doCreate = /*#__PURE__*/function () {
|
|
802
|
-
var
|
|
830
|
+
var _ref10 = _asyncToGenerator(function* () {
|
|
803
831
|
var form;
|
|
804
832
|
_this9.created_at = new SDate().now();
|
|
805
833
|
args = _.defaults(args, {
|
|
@@ -845,7 +873,7 @@ class SpiceModel {
|
|
|
845
873
|
});
|
|
846
874
|
});
|
|
847
875
|
return function doCreate() {
|
|
848
|
-
return
|
|
876
|
+
return _ref10.apply(this, arguments);
|
|
849
877
|
};
|
|
850
878
|
}();
|
|
851
879
|
try {
|
|
@@ -883,7 +911,7 @@ class SpiceModel {
|
|
|
883
911
|
// Profiling: use track() for proper async context forking
|
|
884
912
|
var p = (_this1$_ctx = _this1[_ctx]) == null ? void 0 : _this1$_ctx.profiler;
|
|
885
913
|
var doDelete = /*#__PURE__*/function () {
|
|
886
|
-
var
|
|
914
|
+
var _ref12 = _asyncToGenerator(function* () {
|
|
887
915
|
var item_exist = yield _this1.exist(args.id);
|
|
888
916
|
if (!item_exist) {
|
|
889
917
|
throw new Error(_this1.type + " does not exist.");
|
|
@@ -894,7 +922,7 @@ class SpiceModel {
|
|
|
894
922
|
}
|
|
895
923
|
var delete_response = {};
|
|
896
924
|
var dbOperation = /*#__PURE__*/function () {
|
|
897
|
-
var
|
|
925
|
+
var _ref13 = _asyncToGenerator(function* () {
|
|
898
926
|
if (args.hard) {
|
|
899
927
|
delete_response = yield _this1.database.delete(args.id);
|
|
900
928
|
_this1.setMonitor();
|
|
@@ -905,7 +933,7 @@ class SpiceModel {
|
|
|
905
933
|
}
|
|
906
934
|
});
|
|
907
935
|
return function dbOperation() {
|
|
908
|
-
return
|
|
936
|
+
return _ref13.apply(this, arguments);
|
|
909
937
|
};
|
|
910
938
|
}();
|
|
911
939
|
if (p) {
|
|
@@ -914,12 +942,14 @@ class SpiceModel {
|
|
|
914
942
|
yield dbOperation();
|
|
915
943
|
}
|
|
916
944
|
if (args.skip_hooks != true) {
|
|
917
|
-
yield _this1.run_hook(
|
|
945
|
+
yield _this1.run_hook(_extends({}, results, {
|
|
946
|
+
id: args.id
|
|
947
|
+
}), "delete", "after", results);
|
|
918
948
|
}
|
|
919
949
|
return {};
|
|
920
950
|
});
|
|
921
951
|
return function doDelete() {
|
|
922
|
-
return
|
|
952
|
+
return _ref12.apply(this, arguments);
|
|
923
953
|
};
|
|
924
954
|
}();
|
|
925
955
|
try {
|
|
@@ -1109,12 +1139,12 @@ class SpiceModel {
|
|
|
1109
1139
|
}
|
|
1110
1140
|
createJoinSection(mappedNestings) {
|
|
1111
1141
|
if (!mappedNestings || mappedNestings.length === 0) return "";
|
|
1112
|
-
return mappedNestings.map(
|
|
1142
|
+
return mappedNestings.map(_ref14 => {
|
|
1113
1143
|
var {
|
|
1114
1144
|
alias,
|
|
1115
1145
|
reference,
|
|
1116
1146
|
is_array
|
|
1117
|
-
} =
|
|
1147
|
+
} = _ref14;
|
|
1118
1148
|
var keyspace = (0, _fix.fixCollection)(reference);
|
|
1119
1149
|
if (is_array === true) {
|
|
1120
1150
|
return "LEFT NEST `" + keyspace + "` AS `" + alias + "` ON KEYS `" + this.type + "`.`" + alias + "`";
|
|
@@ -1259,10 +1289,9 @@ class SpiceModel {
|
|
|
1259
1289
|
// Profiling: use track() for proper async context forking
|
|
1260
1290
|
var p = (_this10$_ctx = _this10[_ctx]) == null ? void 0 : _this10$_ctx.profiler;
|
|
1261
1291
|
var doList = /*#__PURE__*/function () {
|
|
1262
|
-
var
|
|
1263
|
-
var
|
|
1264
|
-
|
|
1265
|
-
if (args.mapping_dept_exempt) _this10[_mapping_dept_exempt] = args.mapping_dept_exempt;
|
|
1292
|
+
var _ref15 = _asyncToGenerator(function* () {
|
|
1293
|
+
var _args1, _args10;
|
|
1294
|
+
_this10.applyMappingDepthArgs(args);
|
|
1266
1295
|
|
|
1267
1296
|
// Filter out id, meta().id, and any column ending with .id (e.g., dependents.id)
|
|
1268
1297
|
if (args.columns) {
|
|
@@ -1276,9 +1305,9 @@ class SpiceModel {
|
|
|
1276
1305
|
var originalColumns = args.columns;
|
|
1277
1306
|
|
|
1278
1307
|
// Find alias tokens from query/columns/sort
|
|
1279
|
-
var nestings = [..._this10.extractNestings(((
|
|
1308
|
+
var nestings = [..._this10.extractNestings(((_args1 = args) == null ? void 0 : _args1.query) || "", _this10.type),
|
|
1280
1309
|
//...this.extractNestings(args?.columns || "", this.type),
|
|
1281
|
-
..._this10.extractNestings(((
|
|
1310
|
+
..._this10.extractNestings(((_args10 = args) == null ? void 0 : _args10.sort) || "", _this10.type)];
|
|
1282
1311
|
|
|
1283
1312
|
// Build join metadata and prepare columns
|
|
1284
1313
|
var {
|
|
@@ -1339,15 +1368,15 @@ class SpiceModel {
|
|
|
1339
1368
|
return results;
|
|
1340
1369
|
});
|
|
1341
1370
|
return function doList() {
|
|
1342
|
-
return
|
|
1371
|
+
return _ref15.apply(this, arguments);
|
|
1343
1372
|
};
|
|
1344
1373
|
}();
|
|
1345
1374
|
try {
|
|
1346
1375
|
if (p) {
|
|
1347
|
-
var
|
|
1376
|
+
var _args11, _args12;
|
|
1348
1377
|
return yield p.track(_this10.type + ".list", doList, {
|
|
1349
|
-
limit: (
|
|
1350
|
-
offset: (
|
|
1378
|
+
limit: (_args11 = args) == null ? void 0 : _args11.limit,
|
|
1379
|
+
offset: (_args12 = args) == null ? void 0 : _args12.offset
|
|
1351
1380
|
});
|
|
1352
1381
|
}
|
|
1353
1382
|
return yield doList();
|
|
@@ -1393,8 +1422,9 @@ class SpiceModel {
|
|
|
1393
1422
|
// Profiling: use track() for proper async context forking
|
|
1394
1423
|
var p = (_this11$_ctx = _this11[_ctx]) == null ? void 0 : _this11$_ctx.profiler;
|
|
1395
1424
|
var doFetch = /*#__PURE__*/function () {
|
|
1396
|
-
var
|
|
1425
|
+
var _ref16 = _asyncToGenerator(function* () {
|
|
1397
1426
|
if (args.is_custom_query === "true" && args.ids.length > 0) {
|
|
1427
|
+
console.log("Fetching results from database", _this11.type, args, query, mappedNestings);
|
|
1398
1428
|
return yield _this11.database.query(query);
|
|
1399
1429
|
} else if (args.is_full_text === "true") {
|
|
1400
1430
|
return yield _this11.database.full_text_search(_this11.type, query || "", args.limit, args.offset, args._join);
|
|
@@ -1405,7 +1435,7 @@ class SpiceModel {
|
|
|
1405
1435
|
}
|
|
1406
1436
|
});
|
|
1407
1437
|
return function doFetch() {
|
|
1408
|
-
return
|
|
1438
|
+
return _ref16.apply(this, arguments);
|
|
1409
1439
|
};
|
|
1410
1440
|
}();
|
|
1411
1441
|
if (p) {
|
|
@@ -1414,12 +1444,12 @@ class SpiceModel {
|
|
|
1414
1444
|
return yield doFetch();
|
|
1415
1445
|
})();
|
|
1416
1446
|
}
|
|
1417
|
-
addHook(
|
|
1447
|
+
addHook(_ref17) {
|
|
1418
1448
|
var {
|
|
1419
1449
|
operation,
|
|
1420
1450
|
when,
|
|
1421
1451
|
execute
|
|
1422
|
-
} =
|
|
1452
|
+
} = _ref17;
|
|
1423
1453
|
this[_hooks][operation][when].push(execute);
|
|
1424
1454
|
}
|
|
1425
1455
|
run_hook(data, op, when, old_data) {
|
|
@@ -1517,7 +1547,8 @@ class SpiceModel {
|
|
|
1517
1547
|
data = Array.of(data);
|
|
1518
1548
|
}
|
|
1519
1549
|
var isExempt = _this13.isFieldExempt(source_property);
|
|
1520
|
-
|
|
1550
|
+
var activeMappingDept = type === "read" ? _this13[_mapping_dept_scalar] : _this13[_mapping_dept];
|
|
1551
|
+
if (isExempt || _this13[_level] + 1 < activeMappingDept) {
|
|
1521
1552
|
var classes = _.compact(_.isArray(Class) ? Class : [Class]);
|
|
1522
1553
|
var ids = [];
|
|
1523
1554
|
_.each(data, result => {
|
|
@@ -1541,12 +1572,14 @@ class SpiceModel {
|
|
|
1541
1572
|
|
|
1542
1573
|
// ⚡ Wrap in profiler track() to ensure proper async context for child operations
|
|
1543
1574
|
var fetchRelated = /*#__PURE__*/function () {
|
|
1544
|
-
var
|
|
1575
|
+
var _ref18 = _asyncToGenerator(function* () {
|
|
1545
1576
|
return yield Promise.allSettled(_.map(classes, obj => {
|
|
1546
1577
|
var objInstance = new obj(_extends({}, _this13[_args], {
|
|
1547
1578
|
skip_cache: _this13[_skip_cache],
|
|
1548
1579
|
_level: _this13[_level] + 1,
|
|
1549
1580
|
mapping_dept: _this13[_mapping_dept],
|
|
1581
|
+
mapping_dept_scalar: _this13[_mapping_dept_scalar],
|
|
1582
|
+
mapping_dept_array: _this13[_mapping_dept_array],
|
|
1550
1583
|
mapping_dept_exempt: _this13[_mapping_dept_exempt],
|
|
1551
1584
|
_columns: columnArray.join(","),
|
|
1552
1585
|
_current_path: childPath
|
|
@@ -1559,7 +1592,7 @@ class SpiceModel {
|
|
|
1559
1592
|
}));
|
|
1560
1593
|
});
|
|
1561
1594
|
return function fetchRelated() {
|
|
1562
|
-
return
|
|
1595
|
+
return _ref18.apply(this, arguments);
|
|
1563
1596
|
};
|
|
1564
1597
|
}();
|
|
1565
1598
|
var returned_all;
|
|
@@ -1626,7 +1659,8 @@ class SpiceModel {
|
|
|
1626
1659
|
data = Array.of(data);
|
|
1627
1660
|
}
|
|
1628
1661
|
var isExempt = _this14.isFieldExempt(source_property);
|
|
1629
|
-
|
|
1662
|
+
var activeMappingDept = type === "read" ? _this14[_mapping_dept_array] : _this14[_mapping_dept];
|
|
1663
|
+
if (isExempt || _this14[_level] + 1 < activeMappingDept) {
|
|
1630
1664
|
var ids = [];
|
|
1631
1665
|
_.each(data, result => {
|
|
1632
1666
|
var value = result[source_property];
|
|
@@ -1659,12 +1693,14 @@ class SpiceModel {
|
|
|
1659
1693
|
|
|
1660
1694
|
// ⚡ Wrap in profiler track() to ensure proper async context for child operations
|
|
1661
1695
|
var fetchRelated = /*#__PURE__*/function () {
|
|
1662
|
-
var
|
|
1696
|
+
var _ref19 = _asyncToGenerator(function* () {
|
|
1663
1697
|
return yield Promise.allSettled(_.map(classes, obj => {
|
|
1664
1698
|
return new obj(_extends({}, _this14[_args], {
|
|
1665
1699
|
skip_cache: _this14[_skip_cache],
|
|
1666
1700
|
_level: _this14[_level] + 1,
|
|
1667
1701
|
mapping_dept: _this14[_mapping_dept],
|
|
1702
|
+
mapping_dept_scalar: _this14[_mapping_dept_scalar],
|
|
1703
|
+
mapping_dept_array: _this14[_mapping_dept_array],
|
|
1668
1704
|
mapping_dept_exempt: _this14[_mapping_dept_exempt],
|
|
1669
1705
|
_columns: columnArray.join(","),
|
|
1670
1706
|
_current_path: childPath
|
|
@@ -1676,7 +1712,7 @@ class SpiceModel {
|
|
|
1676
1712
|
}));
|
|
1677
1713
|
});
|
|
1678
1714
|
return function fetchRelated() {
|
|
1679
|
-
return
|
|
1715
|
+
return _ref19.apply(this, arguments);
|
|
1680
1716
|
};
|
|
1681
1717
|
}();
|
|
1682
1718
|
var returned_all;
|
|
@@ -1724,11 +1760,11 @@ class SpiceModel {
|
|
|
1724
1760
|
return original_is_array ? data : data[0];
|
|
1725
1761
|
})();
|
|
1726
1762
|
}
|
|
1727
|
-
addModifier(
|
|
1763
|
+
addModifier(_ref20) {
|
|
1728
1764
|
var {
|
|
1729
1765
|
when,
|
|
1730
1766
|
execute
|
|
1731
|
-
} =
|
|
1767
|
+
} = _ref20;
|
|
1732
1768
|
if (this[_serializers][when]) {
|
|
1733
1769
|
this[_serializers][when]["modifiers"].push(execute);
|
|
1734
1770
|
}
|
|
@@ -1798,14 +1834,14 @@ class SpiceModel {
|
|
|
1798
1834
|
_this15.addModifier({
|
|
1799
1835
|
when: properties[i].map.when || "read",
|
|
1800
1836
|
execute: function () {
|
|
1801
|
-
var _execute = _asyncToGenerator(function* (data, old_data, ctx, type, args, mapping_dept, level, columns) {
|
|
1837
|
+
var _execute = _asyncToGenerator(function* (data, old_data, ctx, type, args, mapping_dept, level, columns, serializationType) {
|
|
1802
1838
|
// Skip if columns are specified but this field isn't included
|
|
1803
1839
|
if (!_this15.isFieldInColumns(i, columns)) {
|
|
1804
1840
|
return data;
|
|
1805
1841
|
}
|
|
1806
|
-
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,
|
|
1842
|
+
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);
|
|
1807
1843
|
});
|
|
1808
|
-
function execute(_x, _x2, _x3, _x4, _x5, _x6, _x7, _x8) {
|
|
1844
|
+
function execute(_x, _x2, _x3, _x4, _x5, _x6, _x7, _x8, _x9) {
|
|
1809
1845
|
return _execute.apply(this, arguments);
|
|
1810
1846
|
}
|
|
1811
1847
|
return execute;
|
|
@@ -1819,14 +1855,14 @@ class SpiceModel {
|
|
|
1819
1855
|
_this15.addModifier({
|
|
1820
1856
|
when: properties[i].map.when || "read",
|
|
1821
1857
|
execute: function () {
|
|
1822
|
-
var _execute2 = _asyncToGenerator(function* (data, old_data, ctx, type, args, mapping_dept, level, columns) {
|
|
1858
|
+
var _execute2 = _asyncToGenerator(function* (data, old_data, ctx, type, args, mapping_dept, level, columns, serializationType) {
|
|
1823
1859
|
// Skip if columns are specified but this field isn't included
|
|
1824
1860
|
if (!_this15.isFieldInColumns(i, columns)) {
|
|
1825
1861
|
return data;
|
|
1826
1862
|
}
|
|
1827
|
-
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,
|
|
1863
|
+
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);
|
|
1828
1864
|
});
|
|
1829
|
-
function execute(
|
|
1865
|
+
function execute(_x0, _x1, _x10, _x11, _x12, _x13, _x14, _x15, _x16) {
|
|
1830
1866
|
return _execute2.apply(this, arguments);
|
|
1831
1867
|
}
|
|
1832
1868
|
return execute;
|
|
@@ -1858,7 +1894,7 @@ class SpiceModel {
|
|
|
1858
1894
|
// Profiling: use track() for proper async context forking
|
|
1859
1895
|
var p = (_this16$_ctx = _this16[_ctx]) == null ? void 0 : _this16$_ctx.profiler;
|
|
1860
1896
|
var doSerialize = /*#__PURE__*/function () {
|
|
1861
|
-
var
|
|
1897
|
+
var _ref21 = _asyncToGenerator(function* () {
|
|
1862
1898
|
var _this16$_serializers, _this16$_serializers$;
|
|
1863
1899
|
// Early exit if serialization should not run.
|
|
1864
1900
|
if (!_this16.shouldSerializerRun(args, type)) {
|
|
@@ -1876,7 +1912,7 @@ class SpiceModel {
|
|
|
1876
1912
|
for (var i = 0; i < modifiers.length; i++) {
|
|
1877
1913
|
var modifier = modifiers[i];
|
|
1878
1914
|
try {
|
|
1879
|
-
var result = yield modifier(data, old_data, _this16[_ctx], _this16.type, args, _this16[_mapping_dept], _this16[_level], _this16[_columns]);
|
|
1915
|
+
var result = yield modifier(data, old_data, _this16[_ctx], _this16.type, args, _this16[_mapping_dept], _this16[_level], _this16[_columns], type);
|
|
1880
1916
|
// Guard against modifiers that return undefined
|
|
1881
1917
|
if (result !== undefined) {
|
|
1882
1918
|
data = result;
|
|
@@ -1926,7 +1962,7 @@ class SpiceModel {
|
|
|
1926
1962
|
return originalIsArray ? data : data[0];
|
|
1927
1963
|
});
|
|
1928
1964
|
return function doSerialize() {
|
|
1929
|
-
return
|
|
1965
|
+
return _ref21.apply(this, arguments);
|
|
1930
1966
|
};
|
|
1931
1967
|
}();
|
|
1932
1968
|
try {
|
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
var path = require("path");
|
|
3
3
|
//import IO from "koa-socket-2";
|
|
4
4
|
const cors = require("kcors");
|
|
5
|
-
|
|
5
|
+
// Preserve an existing host/test configuration when the package is imported.
|
|
6
|
+
// Production boot still starts from an empty object because spice is undefined.
|
|
7
|
+
global.spice = global.spice || {};
|
|
6
8
|
|
|
7
9
|
require("dotenv").config({ path: path.resolve("./") });
|
|
8
10
|
|
package/src/models/SpiceModel.js
CHANGED
|
@@ -22,6 +22,8 @@ var SDate = require("sonover-date"),
|
|
|
22
22
|
_serializers = Symbol(),
|
|
23
23
|
_level = Symbol(),
|
|
24
24
|
_mapping_dept = Symbol(),
|
|
25
|
+
_mapping_dept_scalar = Symbol(),
|
|
26
|
+
_mapping_dept_array = Symbol(),
|
|
25
27
|
_mapping_dept_exempt = Symbol(),
|
|
26
28
|
_current_path = Symbol();
|
|
27
29
|
|
|
@@ -47,12 +49,29 @@ if (!Promise.allSettled) {
|
|
|
47
49
|
export default class SpiceModel {
|
|
48
50
|
constructor(args = {}) {
|
|
49
51
|
try {
|
|
52
|
+
const modelArgs = args?.args || {};
|
|
53
|
+
const requestProfile = modelArgs?.ctx?.state?.read_profile || {};
|
|
54
|
+
const defaultMappingDept = process.env.MAPPING_DEPT || 3;
|
|
55
|
+
const legacyMappingDept =
|
|
56
|
+
modelArgs.mapping_dept !== undefined && modelArgs.mapping_dept !== null ?
|
|
57
|
+
modelArgs.mapping_dept
|
|
58
|
+
: undefined;
|
|
59
|
+
|
|
50
60
|
var dbtype =
|
|
51
61
|
spice.config.database.connections[args.connection].type || "couchbase";
|
|
52
62
|
let Database = require(`spice-${dbtype}`);
|
|
53
|
-
this[_mapping_dept] =
|
|
54
|
-
|
|
55
|
-
|
|
63
|
+
this[_mapping_dept] = legacyMappingDept ?? defaultMappingDept;
|
|
64
|
+
this[_mapping_dept_scalar] =
|
|
65
|
+
modelArgs.mapping_dept_scalar ??
|
|
66
|
+
legacyMappingDept ??
|
|
67
|
+
requestProfile.mapping_dept_scalar ??
|
|
68
|
+
defaultMappingDept;
|
|
69
|
+
this[_mapping_dept_array] =
|
|
70
|
+
modelArgs.mapping_dept_array ??
|
|
71
|
+
legacyMappingDept ??
|
|
72
|
+
requestProfile.mapping_dept_array ??
|
|
73
|
+
defaultMappingDept;
|
|
74
|
+
this[_mapping_dept_exempt] = modelArgs.mapping_dept_exempt || [];
|
|
56
75
|
this.type = "";
|
|
57
76
|
this.collection = args.connection;
|
|
58
77
|
this[_args] = args.args;
|
|
@@ -237,6 +256,32 @@ export default class SpiceModel {
|
|
|
237
256
|
// }
|
|
238
257
|
}
|
|
239
258
|
|
|
259
|
+
applyMappingDepthArgs(args = {}) {
|
|
260
|
+
const hasLegacyDepth =
|
|
261
|
+
args.mapping_dept !== undefined && args.mapping_dept !== null;
|
|
262
|
+
|
|
263
|
+
if (hasLegacyDepth) {
|
|
264
|
+
this[_mapping_dept] = args.mapping_dept;
|
|
265
|
+
this[_mapping_dept_scalar] = args.mapping_dept;
|
|
266
|
+
this[_mapping_dept_array] = args.mapping_dept;
|
|
267
|
+
}
|
|
268
|
+
if (
|
|
269
|
+
args.mapping_dept_scalar !== undefined &&
|
|
270
|
+
args.mapping_dept_scalar !== null
|
|
271
|
+
) {
|
|
272
|
+
this[_mapping_dept_scalar] = args.mapping_dept_scalar;
|
|
273
|
+
}
|
|
274
|
+
if (
|
|
275
|
+
args.mapping_dept_array !== undefined &&
|
|
276
|
+
args.mapping_dept_array !== null
|
|
277
|
+
) {
|
|
278
|
+
this[_mapping_dept_array] = args.mapping_dept_array;
|
|
279
|
+
}
|
|
280
|
+
if (args.mapping_dept_exempt) {
|
|
281
|
+
this[_mapping_dept_exempt] = args.mapping_dept_exempt;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
240
285
|
get database() {
|
|
241
286
|
return this[_database];
|
|
242
287
|
}
|
|
@@ -515,13 +560,10 @@ export default class SpiceModel {
|
|
|
515
560
|
const p = this[_ctx]?.profiler;
|
|
516
561
|
|
|
517
562
|
const doGet = async () => {
|
|
518
|
-
if (args.mapping_dept) this[_mapping_dept] = args.mapping_dept;
|
|
519
|
-
if (args.mapping_dept_exempt)
|
|
520
|
-
this[_mapping_dept_exempt] = args.mapping_dept_exempt;
|
|
521
|
-
|
|
522
563
|
if (!args) {
|
|
523
564
|
args = {};
|
|
524
565
|
}
|
|
566
|
+
this.applyMappingDepthArgs(args);
|
|
525
567
|
if (_.isString(args.id)) {
|
|
526
568
|
if (args.skip_hooks !== true) {
|
|
527
569
|
await this.run_hook(args, "get", "before");
|
|
@@ -687,10 +729,13 @@ export default class SpiceModel {
|
|
|
687
729
|
if (!args) {
|
|
688
730
|
args = {};
|
|
689
731
|
}
|
|
732
|
+
this.applyMappingDepthArgs(args);
|
|
690
733
|
if (args.skip_hooks != true) {
|
|
691
734
|
await this.run_hook(this, "list", "before");
|
|
692
735
|
}
|
|
693
|
-
|
|
736
|
+
args.ids = _.uniq(
|
|
737
|
+
_.filter(args.ids || [], (id) => _.isString(id) && id.length > 0),
|
|
738
|
+
);
|
|
694
739
|
let key = `multi-get::${this.type}::${_.join(args.ids, "|")}:${args.columns}`;
|
|
695
740
|
let results = [];
|
|
696
741
|
|
|
@@ -995,7 +1040,12 @@ export default class SpiceModel {
|
|
|
995
1040
|
}
|
|
996
1041
|
|
|
997
1042
|
if (args.skip_hooks != true) {
|
|
998
|
-
await this.run_hook(
|
|
1043
|
+
await this.run_hook(
|
|
1044
|
+
{ ...results, id: args.id },
|
|
1045
|
+
"delete",
|
|
1046
|
+
"after",
|
|
1047
|
+
results,
|
|
1048
|
+
);
|
|
999
1049
|
}
|
|
1000
1050
|
return {};
|
|
1001
1051
|
};
|
|
@@ -1379,9 +1429,7 @@ export default class SpiceModel {
|
|
|
1379
1429
|
const p = this[_ctx]?.profiler;
|
|
1380
1430
|
|
|
1381
1431
|
const doList = async () => {
|
|
1382
|
-
|
|
1383
|
-
if (args.mapping_dept_exempt)
|
|
1384
|
-
this[_mapping_dept_exempt] = args.mapping_dept_exempt;
|
|
1432
|
+
this.applyMappingDepthArgs(args);
|
|
1385
1433
|
|
|
1386
1434
|
// Filter out id, meta().id, and any column ending with .id (e.g., dependents.id)
|
|
1387
1435
|
if (args.columns) {
|
|
@@ -1553,6 +1601,13 @@ export default class SpiceModel {
|
|
|
1553
1601
|
|
|
1554
1602
|
const doFetch = async () => {
|
|
1555
1603
|
if (args.is_custom_query === "true" && args.ids.length > 0) {
|
|
1604
|
+
console.log(
|
|
1605
|
+
"Fetching results from database",
|
|
1606
|
+
this.type,
|
|
1607
|
+
args,
|
|
1608
|
+
query,
|
|
1609
|
+
mappedNestings,
|
|
1610
|
+
);
|
|
1556
1611
|
return await this.database.query(query);
|
|
1557
1612
|
} else if (args.is_full_text === "true") {
|
|
1558
1613
|
return await this.database.full_text_search(
|
|
@@ -1712,7 +1767,9 @@ export default class SpiceModel {
|
|
|
1712
1767
|
data = Array.of(data);
|
|
1713
1768
|
}
|
|
1714
1769
|
const isExempt = this.isFieldExempt(source_property);
|
|
1715
|
-
|
|
1770
|
+
const activeMappingDept =
|
|
1771
|
+
type === "read" ? this[_mapping_dept_scalar] : this[_mapping_dept];
|
|
1772
|
+
if (isExempt || this[_level] + 1 < activeMappingDept) {
|
|
1716
1773
|
let classes = _.compact(_.isArray(Class) ? Class : [Class]);
|
|
1717
1774
|
|
|
1718
1775
|
let ids = [];
|
|
@@ -1756,6 +1813,8 @@ export default class SpiceModel {
|
|
|
1756
1813
|
skip_cache: this[_skip_cache],
|
|
1757
1814
|
_level: this[_level] + 1,
|
|
1758
1815
|
mapping_dept: this[_mapping_dept],
|
|
1816
|
+
mapping_dept_scalar: this[_mapping_dept_scalar],
|
|
1817
|
+
mapping_dept_array: this[_mapping_dept_array],
|
|
1759
1818
|
mapping_dept_exempt: this[_mapping_dept_exempt],
|
|
1760
1819
|
_columns: columnArray.join(","),
|
|
1761
1820
|
_current_path: childPath,
|
|
@@ -1876,7 +1935,9 @@ export default class SpiceModel {
|
|
|
1876
1935
|
data = Array.of(data);
|
|
1877
1936
|
}
|
|
1878
1937
|
const isExempt = this.isFieldExempt(source_property);
|
|
1879
|
-
|
|
1938
|
+
const activeMappingDept =
|
|
1939
|
+
type === "read" ? this[_mapping_dept_array] : this[_mapping_dept];
|
|
1940
|
+
if (isExempt || this[_level] + 1 < activeMappingDept) {
|
|
1880
1941
|
let ids = [];
|
|
1881
1942
|
_.each(data, (result) => {
|
|
1882
1943
|
let value = result[source_property];
|
|
@@ -1927,6 +1988,8 @@ export default class SpiceModel {
|
|
|
1927
1988
|
skip_cache: this[_skip_cache],
|
|
1928
1989
|
_level: this[_level] + 1,
|
|
1929
1990
|
mapping_dept: this[_mapping_dept],
|
|
1991
|
+
mapping_dept_scalar: this[_mapping_dept_scalar],
|
|
1992
|
+
mapping_dept_array: this[_mapping_dept_array],
|
|
1930
1993
|
mapping_dept_exempt: this[_mapping_dept_exempt],
|
|
1931
1994
|
_columns: columnArray.join(","),
|
|
1932
1995
|
_current_path: childPath,
|
|
@@ -2091,6 +2154,7 @@ export default class SpiceModel {
|
|
|
2091
2154
|
mapping_dept,
|
|
2092
2155
|
level,
|
|
2093
2156
|
columns,
|
|
2157
|
+
serializationType,
|
|
2094
2158
|
) => {
|
|
2095
2159
|
// Skip if columns are specified but this field isn't included
|
|
2096
2160
|
if (!this.isFieldInColumns(i, columns)) {
|
|
@@ -2105,7 +2169,7 @@ export default class SpiceModel {
|
|
|
2105
2169
|
properties[i].map.destination || i,
|
|
2106
2170
|
properties[i],
|
|
2107
2171
|
args,
|
|
2108
|
-
|
|
2172
|
+
serializationType,
|
|
2109
2173
|
mapping_dept,
|
|
2110
2174
|
level,
|
|
2111
2175
|
columns,
|
|
@@ -2127,6 +2191,7 @@ export default class SpiceModel {
|
|
|
2127
2191
|
mapping_dept,
|
|
2128
2192
|
level,
|
|
2129
2193
|
columns,
|
|
2194
|
+
serializationType,
|
|
2130
2195
|
) => {
|
|
2131
2196
|
// Skip if columns are specified but this field isn't included
|
|
2132
2197
|
if (!this.isFieldInColumns(i, columns)) {
|
|
@@ -2142,7 +2207,7 @@ export default class SpiceModel {
|
|
|
2142
2207
|
properties[i].map.destination || i,
|
|
2143
2208
|
properties[i],
|
|
2144
2209
|
args,
|
|
2145
|
-
|
|
2210
|
+
serializationType,
|
|
2146
2211
|
mapping_dept,
|
|
2147
2212
|
level,
|
|
2148
2213
|
columns,
|
|
@@ -2192,6 +2257,7 @@ export default class SpiceModel {
|
|
|
2192
2257
|
this[_mapping_dept],
|
|
2193
2258
|
this[_level],
|
|
2194
2259
|
this[_columns],
|
|
2260
|
+
type,
|
|
2195
2261
|
);
|
|
2196
2262
|
// Guard against modifiers that return undefined
|
|
2197
2263
|
if (result !== undefined) {
|
|
@@ -22,8 +22,10 @@ const spiceConfig = {
|
|
|
22
22
|
cache: {},
|
|
23
23
|
cache_providers: {},
|
|
24
24
|
schemas: {},
|
|
25
|
+
models: {},
|
|
25
26
|
modifiers: {},
|
|
26
|
-
schema_extenders: []
|
|
27
|
+
schema_extenders: [],
|
|
28
|
+
getModifiers: jest.fn(() => [])
|
|
27
29
|
};
|
|
28
30
|
|
|
29
31
|
// Set on multiple global scopes to ensure it's available
|
|
@@ -29,6 +29,8 @@ const columnTestCases = {
|
|
|
29
29
|
*/
|
|
30
30
|
const sampleDbResults = {
|
|
31
31
|
users: [
|
|
32
|
+
{ id: 'valid-1', type: 'user', name: 'Valid One', email: 'valid1@example.com' },
|
|
33
|
+
{ id: 'valid-2', type: 'user', name: 'Valid Two', email: 'valid2@example.com' },
|
|
32
34
|
{ id: 'user-1', type: 'user', name: 'Alice', email: 'alice@example.com' },
|
|
33
35
|
{ id: 'user-2', type: 'user', name: 'Bob', email: 'bob@example.com' },
|
|
34
36
|
{ id: 'user-3', type: 'user', name: 'Charlie', email: 'charlie@example.com' }
|
|
@@ -71,7 +73,7 @@ function generateLargeDataset(size = 100) {
|
|
|
71
73
|
for (let i = 0; i < size; i++) {
|
|
72
74
|
data.push({
|
|
73
75
|
id: `item-${i}`,
|
|
74
|
-
type: '
|
|
76
|
+
type: 'user',
|
|
75
77
|
name: `Item ${i}`,
|
|
76
78
|
value: Math.random()
|
|
77
79
|
});
|
|
@@ -4,6 +4,12 @@
|
|
|
4
4
|
|
|
5
5
|
const SpiceModel = require('../../src/models/SpiceModel').default;
|
|
6
6
|
const MockDatabase = require('../__mocks__/spice-couchbase');
|
|
7
|
+
const spiceConfig = require('../__mocks__/globalMocks');
|
|
8
|
+
|
|
9
|
+
// Importing SpiceModel loads the package index, which initializes global.spice.
|
|
10
|
+
// Restore the complete test configuration after that package initialization.
|
|
11
|
+
global.spice = spiceConfig;
|
|
12
|
+
globalThis.spice = spiceConfig;
|
|
7
13
|
|
|
8
14
|
/**
|
|
9
15
|
* Create a test model instance with mocked database
|
|
@@ -32,7 +38,7 @@ function createTestModel(config = {}) {
|
|
|
32
38
|
connection,
|
|
33
39
|
collection: type,
|
|
34
40
|
props,
|
|
35
|
-
args: { ...args, skip_cache: true }
|
|
41
|
+
args: { ...args, skip_cache: true, disable_lifecycle_events: true }
|
|
36
42
|
});
|
|
37
43
|
|
|
38
44
|
// Inject mock database
|
package/tests/jest-env.js
CHANGED
|
@@ -21,8 +21,10 @@ class CustomEnvironment extends NodeEnvironment {
|
|
|
21
21
|
cache: {},
|
|
22
22
|
cache_providers: {},
|
|
23
23
|
schemas: {},
|
|
24
|
+
models: {},
|
|
24
25
|
modifiers: {},
|
|
25
|
-
schema_extenders: []
|
|
26
|
+
schema_extenders: [],
|
|
27
|
+
getModifiers: () => []
|
|
26
28
|
};
|
|
27
29
|
|
|
28
30
|
// Set on the global object for the test environment
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
const { createTestModel, seedDatabase } = require('../helpers/modelFactory');
|
|
2
|
+
|
|
3
|
+
describe('SpiceModel type-specific mapping depth', () => {
|
|
4
|
+
function createRelatedModel(records, capture) {
|
|
5
|
+
return class RelatedModel {
|
|
6
|
+
constructor(args) {
|
|
7
|
+
capture.constructorArgs.push(args);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
async getMulti({ ids }) {
|
|
11
|
+
capture.getMultiIds.push(ids);
|
|
12
|
+
return records.filter((record) => ids.includes(record.id));
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function createMappedModel(args = {}) {
|
|
18
|
+
const scalarCapture = { constructorArgs: [], getMultiIds: [] };
|
|
19
|
+
const arrayCapture = { constructorArgs: [], getMultiIds: [] };
|
|
20
|
+
const ScalarModel = createRelatedModel(
|
|
21
|
+
[{ id: 'group-1', name: 'Administrators' }],
|
|
22
|
+
scalarCapture
|
|
23
|
+
);
|
|
24
|
+
const ArrayModel = createRelatedModel(
|
|
25
|
+
[
|
|
26
|
+
{ id: 'child-1', name: 'First' },
|
|
27
|
+
{ id: 'child-2', name: 'Second' }
|
|
28
|
+
],
|
|
29
|
+
arrayCapture
|
|
30
|
+
);
|
|
31
|
+
const model = createTestModel({
|
|
32
|
+
type: 'parent',
|
|
33
|
+
args,
|
|
34
|
+
props: {
|
|
35
|
+
group: {
|
|
36
|
+
type: 'string',
|
|
37
|
+
map: { type: 'model', reference: ScalarModel }
|
|
38
|
+
},
|
|
39
|
+
children: {
|
|
40
|
+
type: 'array',
|
|
41
|
+
map: { type: 'model', reference: ArrayModel }
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
seedDatabase(model, {
|
|
46
|
+
id: 'parent-1',
|
|
47
|
+
type: 'parent',
|
|
48
|
+
group: 'group-1',
|
|
49
|
+
children: ['child-1', 'child-2']
|
|
50
|
+
});
|
|
51
|
+
return { model, scalarCapture, arrayCapture };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
test('uses scalar depth 1 and array depth 3 from the request profile', async () => {
|
|
55
|
+
const ctx = {
|
|
56
|
+
state: {
|
|
57
|
+
read_profile: {
|
|
58
|
+
mapping_dept_scalar: 1,
|
|
59
|
+
mapping_dept_array: 3
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
const { model, scalarCapture, arrayCapture } = createMappedModel({ ctx });
|
|
64
|
+
|
|
65
|
+
const result = await model.get({ id: 'parent-1' });
|
|
66
|
+
|
|
67
|
+
expect(result.group).toBe('group-1');
|
|
68
|
+
expect(result.children).toEqual([
|
|
69
|
+
expect.objectContaining({ id: 'child-1', name: 'First' }),
|
|
70
|
+
expect.objectContaining({ id: 'child-2', name: 'Second' })
|
|
71
|
+
]);
|
|
72
|
+
expect(scalarCapture.getMultiIds).toEqual([]);
|
|
73
|
+
expect(arrayCapture.getMultiIds).toEqual([['child-1', 'child-2']]);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test('keeps legacy mapping_dept behavior for both relationship types', async () => {
|
|
77
|
+
const { model, scalarCapture, arrayCapture } = createMappedModel({
|
|
78
|
+
mapping_dept: 1
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
const result = await model.get({ id: 'parent-1' });
|
|
82
|
+
|
|
83
|
+
expect(result.group).toBe('group-1');
|
|
84
|
+
expect(result.children).toEqual(['child-1', 'child-2']);
|
|
85
|
+
expect(scalarCapture.getMultiIds).toEqual([]);
|
|
86
|
+
expect(arrayCapture.getMultiIds).toEqual([]);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test('explicit read arguments override the request profile independently', async () => {
|
|
90
|
+
const ctx = {
|
|
91
|
+
state: {
|
|
92
|
+
read_profile: {
|
|
93
|
+
mapping_dept_scalar: 1,
|
|
94
|
+
mapping_dept_array: 1
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
const { model } = createMappedModel({ ctx });
|
|
99
|
+
|
|
100
|
+
const result = await model.get({
|
|
101
|
+
id: 'parent-1',
|
|
102
|
+
mapping_dept_scalar: 3,
|
|
103
|
+
mapping_dept_array: 3
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
expect(result.group).toEqual(
|
|
107
|
+
expect.objectContaining({ id: 'group-1', name: 'Administrators' })
|
|
108
|
+
);
|
|
109
|
+
expect(result.children).toHaveLength(2);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test('mapping depth exemptions continue to expand scalar paths', async () => {
|
|
113
|
+
const { model, scalarCapture } = createMappedModel({
|
|
114
|
+
mapping_dept_scalar: 1,
|
|
115
|
+
mapping_dept_array: 3,
|
|
116
|
+
mapping_dept_exempt: ['group']
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
const result = await model.get({ id: 'parent-1' });
|
|
120
|
+
|
|
121
|
+
expect(result.group).toEqual(
|
|
122
|
+
expect.objectContaining({ id: 'group-1', name: 'Administrators' })
|
|
123
|
+
);
|
|
124
|
+
expect(scalarCapture.getMultiIds).toEqual([['group-1']]);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
test('propagates both depths to related model instances', async () => {
|
|
128
|
+
const { model, arrayCapture } = createMappedModel({
|
|
129
|
+
mapping_dept_scalar: 1,
|
|
130
|
+
mapping_dept_array: 3
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
await model.getMulti({ ids: ['parent-1'] });
|
|
134
|
+
|
|
135
|
+
expect(arrayCapture.constructorArgs[0]).toEqual(
|
|
136
|
+
expect.objectContaining({
|
|
137
|
+
mapping_dept_scalar: 1,
|
|
138
|
+
mapping_dept_array: 3,
|
|
139
|
+
_level: 1
|
|
140
|
+
})
|
|
141
|
+
);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
test('does not apply the request read profile to write serialization', async () => {
|
|
145
|
+
const capture = { constructorArgs: [], getMultiIds: [] };
|
|
146
|
+
const RelatedModel = createRelatedModel(
|
|
147
|
+
[{ id: 'group-1', name: 'Administrators' }],
|
|
148
|
+
capture
|
|
149
|
+
);
|
|
150
|
+
const model = createTestModel({
|
|
151
|
+
type: 'parent',
|
|
152
|
+
args: {
|
|
153
|
+
ctx: {
|
|
154
|
+
state: {
|
|
155
|
+
read_profile: {
|
|
156
|
+
mapping_dept_scalar: 1,
|
|
157
|
+
mapping_dept_array: 1
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
props: {
|
|
163
|
+
group: {
|
|
164
|
+
type: 'string',
|
|
165
|
+
map: { type: 'model', reference: RelatedModel, when: 'write' }
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
const result = await model.do_serialize(
|
|
171
|
+
{ id: 'parent-1', group: 'group-1' },
|
|
172
|
+
'write',
|
|
173
|
+
{},
|
|
174
|
+
{}
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
expect(result.group).toEqual(
|
|
178
|
+
expect.objectContaining({ id: 'group-1', name: 'Administrators' })
|
|
179
|
+
);
|
|
180
|
+
expect(capture.getMultiIds).toEqual([['group-1']]);
|
|
181
|
+
});
|
|
182
|
+
});
|