spice-js 2.7.29 → 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 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
- global.spice = {};
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, _args2$args, _args3, _args3$args, _args$args, _args4, _args4$args, _args5, _args5$args, _args6, _args6$args, _args7, _args7$args, _args8, _args8$args;
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] = ((_args2 = args) == null ? void 0 : (_args2$args = _args2.args) == null ? void 0 : _args2$args.mapping_dept) || process.env.MAPPING_DEPT || 3;
55
- this[_mapping_dept_exempt] = ((_args3 = args) == null ? void 0 : (_args3$args = _args3.args) == null ? void 0 : _args3$args.mapping_dept_exempt) || [];
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] = (_args4 = args) == null ? void 0 : (_args4$args = _args4.args) == null ? void 0 : _args4$args.ctx;
62
- this[_skip_cache] = ((_args5 = args) == null ? void 0 : (_args5$args = _args5.args) == null ? void 0 : _args5$args.skip_cache) || false;
63
- this[_level] = ((_args6 = args) == null ? void 0 : (_args6$args = _args6.args) == null ? void 0 : _args6$args._level) || 0;
64
- this[_current_path] = ((_args7 = args) == null ? void 0 : (_args7$args = _args7.args) == null ? void 0 : _args7$args._current_path) || "";
65
- this[_columns] = ((_args8 = args) == null ? void 0 : (_args8$args = _args8.args) == null ? void 0 : _args8$args._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(_ref) {
281
+ createValidationString(_ref5) {
254
282
  var {
255
283
  property_name,
256
284
  props,
257
285
  operation,
258
286
  complete
259
- } = _ref;
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 _ref2 = _asyncToGenerator(function* () {
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 _args9, _results, _results2, _results3, _results4, _results5, _results6;
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(((_args9 = args) == null ? void 0 : _args9.columns) || "", _this4.type);
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 _ref2.apply(this, arguments);
627
+ return _ref6.apply(this, arguments);
601
628
  };
602
629
  }();
603
630
  try {
604
631
  if (p) {
605
- var _args0;
632
+ var _args9;
606
633
  return yield p.track(_this4.type + ".get", doGet, {
607
- id: (_args0 = args) == null ? void 0 : _args0.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 _ref5 = _asyncToGenerator(function* () {
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
- _.remove(args.ids, o => o == undefined);
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 _ref5.apply(this, arguments);
704
+ return _ref9.apply(this, arguments);
677
705
  };
678
706
  }();
679
707
  try {
680
708
  if (p) {
681
- var _args1, _args1$ids;
709
+ var _args0, _args0$ids;
682
710
  return yield p.track(_this6.type + ".getMulti", doGetMulti, {
683
- ids_count: ((_args1 = args) == null ? void 0 : (_args1$ids = _args1.ids) == null ? void 0 : _args1$ids.length) || 0
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 _ref6 = _asyncToGenerator(function* () {
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 _ref6.apply(this, arguments);
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 _ref8 = _asyncToGenerator(function* () {
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 _ref8.apply(this, arguments);
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 _ref0 = _asyncToGenerator(function* () {
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 _ref1 = _asyncToGenerator(function* () {
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 _ref1.apply(this, arguments);
936
+ return _ref13.apply(this, arguments);
909
937
  };
910
938
  }();
911
939
  if (p) {
@@ -921,7 +949,7 @@ class SpiceModel {
921
949
  return {};
922
950
  });
923
951
  return function doDelete() {
924
- return _ref0.apply(this, arguments);
952
+ return _ref12.apply(this, arguments);
925
953
  };
926
954
  }();
927
955
  try {
@@ -1111,12 +1139,12 @@ class SpiceModel {
1111
1139
  }
1112
1140
  createJoinSection(mappedNestings) {
1113
1141
  if (!mappedNestings || mappedNestings.length === 0) return "";
1114
- return mappedNestings.map(_ref10 => {
1142
+ return mappedNestings.map(_ref14 => {
1115
1143
  var {
1116
1144
  alias,
1117
1145
  reference,
1118
1146
  is_array
1119
- } = _ref10;
1147
+ } = _ref14;
1120
1148
  var keyspace = (0, _fix.fixCollection)(reference);
1121
1149
  if (is_array === true) {
1122
1150
  return "LEFT NEST `" + keyspace + "` AS `" + alias + "` ON KEYS `" + this.type + "`.`" + alias + "`";
@@ -1261,10 +1289,9 @@ class SpiceModel {
1261
1289
  // Profiling: use track() for proper async context forking
1262
1290
  var p = (_this10$_ctx = _this10[_ctx]) == null ? void 0 : _this10$_ctx.profiler;
1263
1291
  var doList = /*#__PURE__*/function () {
1264
- var _ref11 = _asyncToGenerator(function* () {
1265
- var _args10, _args11;
1266
- if (args.mapping_dept) _this10[_mapping_dept] = args.mapping_dept;
1267
- 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);
1268
1295
 
1269
1296
  // Filter out id, meta().id, and any column ending with .id (e.g., dependents.id)
1270
1297
  if (args.columns) {
@@ -1278,9 +1305,9 @@ class SpiceModel {
1278
1305
  var originalColumns = args.columns;
1279
1306
 
1280
1307
  // Find alias tokens from query/columns/sort
1281
- var nestings = [..._this10.extractNestings(((_args10 = args) == null ? void 0 : _args10.query) || "", _this10.type),
1308
+ var nestings = [..._this10.extractNestings(((_args1 = args) == null ? void 0 : _args1.query) || "", _this10.type),
1282
1309
  //...this.extractNestings(args?.columns || "", this.type),
1283
- ..._this10.extractNestings(((_args11 = args) == null ? void 0 : _args11.sort) || "", _this10.type)];
1310
+ ..._this10.extractNestings(((_args10 = args) == null ? void 0 : _args10.sort) || "", _this10.type)];
1284
1311
 
1285
1312
  // Build join metadata and prepare columns
1286
1313
  var {
@@ -1341,15 +1368,15 @@ class SpiceModel {
1341
1368
  return results;
1342
1369
  });
1343
1370
  return function doList() {
1344
- return _ref11.apply(this, arguments);
1371
+ return _ref15.apply(this, arguments);
1345
1372
  };
1346
1373
  }();
1347
1374
  try {
1348
1375
  if (p) {
1349
- var _args12, _args13;
1376
+ var _args11, _args12;
1350
1377
  return yield p.track(_this10.type + ".list", doList, {
1351
- limit: (_args12 = args) == null ? void 0 : _args12.limit,
1352
- offset: (_args13 = args) == null ? void 0 : _args13.offset
1378
+ limit: (_args11 = args) == null ? void 0 : _args11.limit,
1379
+ offset: (_args12 = args) == null ? void 0 : _args12.offset
1353
1380
  });
1354
1381
  }
1355
1382
  return yield doList();
@@ -1395,8 +1422,9 @@ class SpiceModel {
1395
1422
  // Profiling: use track() for proper async context forking
1396
1423
  var p = (_this11$_ctx = _this11[_ctx]) == null ? void 0 : _this11$_ctx.profiler;
1397
1424
  var doFetch = /*#__PURE__*/function () {
1398
- var _ref12 = _asyncToGenerator(function* () {
1425
+ var _ref16 = _asyncToGenerator(function* () {
1399
1426
  if (args.is_custom_query === "true" && args.ids.length > 0) {
1427
+ console.log("Fetching results from database", _this11.type, args, query, mappedNestings);
1400
1428
  return yield _this11.database.query(query);
1401
1429
  } else if (args.is_full_text === "true") {
1402
1430
  return yield _this11.database.full_text_search(_this11.type, query || "", args.limit, args.offset, args._join);
@@ -1407,7 +1435,7 @@ class SpiceModel {
1407
1435
  }
1408
1436
  });
1409
1437
  return function doFetch() {
1410
- return _ref12.apply(this, arguments);
1438
+ return _ref16.apply(this, arguments);
1411
1439
  };
1412
1440
  }();
1413
1441
  if (p) {
@@ -1416,12 +1444,12 @@ class SpiceModel {
1416
1444
  return yield doFetch();
1417
1445
  })();
1418
1446
  }
1419
- addHook(_ref13) {
1447
+ addHook(_ref17) {
1420
1448
  var {
1421
1449
  operation,
1422
1450
  when,
1423
1451
  execute
1424
- } = _ref13;
1452
+ } = _ref17;
1425
1453
  this[_hooks][operation][when].push(execute);
1426
1454
  }
1427
1455
  run_hook(data, op, when, old_data) {
@@ -1519,7 +1547,8 @@ class SpiceModel {
1519
1547
  data = Array.of(data);
1520
1548
  }
1521
1549
  var isExempt = _this13.isFieldExempt(source_property);
1522
- if (isExempt || _this13[_level] + 1 < _this13[_mapping_dept]) {
1550
+ var activeMappingDept = type === "read" ? _this13[_mapping_dept_scalar] : _this13[_mapping_dept];
1551
+ if (isExempt || _this13[_level] + 1 < activeMappingDept) {
1523
1552
  var classes = _.compact(_.isArray(Class) ? Class : [Class]);
1524
1553
  var ids = [];
1525
1554
  _.each(data, result => {
@@ -1543,12 +1572,14 @@ class SpiceModel {
1543
1572
 
1544
1573
  // ⚡ Wrap in profiler track() to ensure proper async context for child operations
1545
1574
  var fetchRelated = /*#__PURE__*/function () {
1546
- var _ref14 = _asyncToGenerator(function* () {
1575
+ var _ref18 = _asyncToGenerator(function* () {
1547
1576
  return yield Promise.allSettled(_.map(classes, obj => {
1548
1577
  var objInstance = new obj(_extends({}, _this13[_args], {
1549
1578
  skip_cache: _this13[_skip_cache],
1550
1579
  _level: _this13[_level] + 1,
1551
1580
  mapping_dept: _this13[_mapping_dept],
1581
+ mapping_dept_scalar: _this13[_mapping_dept_scalar],
1582
+ mapping_dept_array: _this13[_mapping_dept_array],
1552
1583
  mapping_dept_exempt: _this13[_mapping_dept_exempt],
1553
1584
  _columns: columnArray.join(","),
1554
1585
  _current_path: childPath
@@ -1561,7 +1592,7 @@ class SpiceModel {
1561
1592
  }));
1562
1593
  });
1563
1594
  return function fetchRelated() {
1564
- return _ref14.apply(this, arguments);
1595
+ return _ref18.apply(this, arguments);
1565
1596
  };
1566
1597
  }();
1567
1598
  var returned_all;
@@ -1628,7 +1659,8 @@ class SpiceModel {
1628
1659
  data = Array.of(data);
1629
1660
  }
1630
1661
  var isExempt = _this14.isFieldExempt(source_property);
1631
- if (isExempt || _this14[_level] + 1 < _this14[_mapping_dept]) {
1662
+ var activeMappingDept = type === "read" ? _this14[_mapping_dept_array] : _this14[_mapping_dept];
1663
+ if (isExempt || _this14[_level] + 1 < activeMappingDept) {
1632
1664
  var ids = [];
1633
1665
  _.each(data, result => {
1634
1666
  var value = result[source_property];
@@ -1661,12 +1693,14 @@ class SpiceModel {
1661
1693
 
1662
1694
  // ⚡ Wrap in profiler track() to ensure proper async context for child operations
1663
1695
  var fetchRelated = /*#__PURE__*/function () {
1664
- var _ref15 = _asyncToGenerator(function* () {
1696
+ var _ref19 = _asyncToGenerator(function* () {
1665
1697
  return yield Promise.allSettled(_.map(classes, obj => {
1666
1698
  return new obj(_extends({}, _this14[_args], {
1667
1699
  skip_cache: _this14[_skip_cache],
1668
1700
  _level: _this14[_level] + 1,
1669
1701
  mapping_dept: _this14[_mapping_dept],
1702
+ mapping_dept_scalar: _this14[_mapping_dept_scalar],
1703
+ mapping_dept_array: _this14[_mapping_dept_array],
1670
1704
  mapping_dept_exempt: _this14[_mapping_dept_exempt],
1671
1705
  _columns: columnArray.join(","),
1672
1706
  _current_path: childPath
@@ -1678,7 +1712,7 @@ class SpiceModel {
1678
1712
  }));
1679
1713
  });
1680
1714
  return function fetchRelated() {
1681
- return _ref15.apply(this, arguments);
1715
+ return _ref19.apply(this, arguments);
1682
1716
  };
1683
1717
  }();
1684
1718
  var returned_all;
@@ -1726,11 +1760,11 @@ class SpiceModel {
1726
1760
  return original_is_array ? data : data[0];
1727
1761
  })();
1728
1762
  }
1729
- addModifier(_ref16) {
1763
+ addModifier(_ref20) {
1730
1764
  var {
1731
1765
  when,
1732
1766
  execute
1733
- } = _ref16;
1767
+ } = _ref20;
1734
1768
  if (this[_serializers][when]) {
1735
1769
  this[_serializers][when]["modifiers"].push(execute);
1736
1770
  }
@@ -1800,14 +1834,14 @@ class SpiceModel {
1800
1834
  _this15.addModifier({
1801
1835
  when: properties[i].map.when || "read",
1802
1836
  execute: function () {
1803
- 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) {
1804
1838
  // Skip if columns are specified but this field isn't included
1805
1839
  if (!_this15.isFieldInColumns(i, columns)) {
1806
1840
  return data;
1807
1841
  }
1808
- 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, type, mapping_dept, level, columns);
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);
1809
1843
  });
1810
- function execute(_x, _x2, _x3, _x4, _x5, _x6, _x7, _x8) {
1844
+ function execute(_x, _x2, _x3, _x4, _x5, _x6, _x7, _x8, _x9) {
1811
1845
  return _execute.apply(this, arguments);
1812
1846
  }
1813
1847
  return execute;
@@ -1821,14 +1855,14 @@ class SpiceModel {
1821
1855
  _this15.addModifier({
1822
1856
  when: properties[i].map.when || "read",
1823
1857
  execute: function () {
1824
- 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) {
1825
1859
  // Skip if columns are specified but this field isn't included
1826
1860
  if (!_this15.isFieldInColumns(i, columns)) {
1827
1861
  return data;
1828
1862
  }
1829
- 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, type, mapping_dept, level, columns);
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);
1830
1864
  });
1831
- function execute(_x9, _x0, _x1, _x10, _x11, _x12, _x13, _x14) {
1865
+ function execute(_x0, _x1, _x10, _x11, _x12, _x13, _x14, _x15, _x16) {
1832
1866
  return _execute2.apply(this, arguments);
1833
1867
  }
1834
1868
  return execute;
@@ -1860,7 +1894,7 @@ class SpiceModel {
1860
1894
  // Profiling: use track() for proper async context forking
1861
1895
  var p = (_this16$_ctx = _this16[_ctx]) == null ? void 0 : _this16$_ctx.profiler;
1862
1896
  var doSerialize = /*#__PURE__*/function () {
1863
- var _ref17 = _asyncToGenerator(function* () {
1897
+ var _ref21 = _asyncToGenerator(function* () {
1864
1898
  var _this16$_serializers, _this16$_serializers$;
1865
1899
  // Early exit if serialization should not run.
1866
1900
  if (!_this16.shouldSerializerRun(args, type)) {
@@ -1878,7 +1912,7 @@ class SpiceModel {
1878
1912
  for (var i = 0; i < modifiers.length; i++) {
1879
1913
  var modifier = modifiers[i];
1880
1914
  try {
1881
- 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);
1882
1916
  // Guard against modifiers that return undefined
1883
1917
  if (result !== undefined) {
1884
1918
  data = result;
@@ -1928,7 +1962,7 @@ class SpiceModel {
1928
1962
  return originalIsArray ? data : data[0];
1929
1963
  });
1930
1964
  return function doSerialize() {
1931
- return _ref17.apply(this, arguments);
1965
+ return _ref21.apply(this, arguments);
1932
1966
  };
1933
1967
  }();
1934
1968
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spice-js",
3
- "version": "2.7.29",
3
+ "version": "2.7.30",
4
4
  "description": "spice",
5
5
  "main": "build/index.js",
6
6
  "repository": {
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
- global.spice = {};
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
 
@@ -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
- args?.args?.mapping_dept || process.env.MAPPING_DEPT || 3;
55
- this[_mapping_dept_exempt] = args?.args?.mapping_dept_exempt || [];
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
- _.remove(args.ids, (o) => o == undefined);
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
 
@@ -1384,9 +1429,7 @@ export default class SpiceModel {
1384
1429
  const p = this[_ctx]?.profiler;
1385
1430
 
1386
1431
  const doList = async () => {
1387
- if (args.mapping_dept) this[_mapping_dept] = args.mapping_dept;
1388
- if (args.mapping_dept_exempt)
1389
- this[_mapping_dept_exempt] = args.mapping_dept_exempt;
1432
+ this.applyMappingDepthArgs(args);
1390
1433
 
1391
1434
  // Filter out id, meta().id, and any column ending with .id (e.g., dependents.id)
1392
1435
  if (args.columns) {
@@ -1558,6 +1601,13 @@ export default class SpiceModel {
1558
1601
 
1559
1602
  const doFetch = async () => {
1560
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
+ );
1561
1611
  return await this.database.query(query);
1562
1612
  } else if (args.is_full_text === "true") {
1563
1613
  return await this.database.full_text_search(
@@ -1717,7 +1767,9 @@ export default class SpiceModel {
1717
1767
  data = Array.of(data);
1718
1768
  }
1719
1769
  const isExempt = this.isFieldExempt(source_property);
1720
- if (isExempt || this[_level] + 1 < this[_mapping_dept]) {
1770
+ const activeMappingDept =
1771
+ type === "read" ? this[_mapping_dept_scalar] : this[_mapping_dept];
1772
+ if (isExempt || this[_level] + 1 < activeMappingDept) {
1721
1773
  let classes = _.compact(_.isArray(Class) ? Class : [Class]);
1722
1774
 
1723
1775
  let ids = [];
@@ -1761,6 +1813,8 @@ export default class SpiceModel {
1761
1813
  skip_cache: this[_skip_cache],
1762
1814
  _level: this[_level] + 1,
1763
1815
  mapping_dept: this[_mapping_dept],
1816
+ mapping_dept_scalar: this[_mapping_dept_scalar],
1817
+ mapping_dept_array: this[_mapping_dept_array],
1764
1818
  mapping_dept_exempt: this[_mapping_dept_exempt],
1765
1819
  _columns: columnArray.join(","),
1766
1820
  _current_path: childPath,
@@ -1881,7 +1935,9 @@ export default class SpiceModel {
1881
1935
  data = Array.of(data);
1882
1936
  }
1883
1937
  const isExempt = this.isFieldExempt(source_property);
1884
- if (isExempt || this[_level] + 1 < this[_mapping_dept]) {
1938
+ const activeMappingDept =
1939
+ type === "read" ? this[_mapping_dept_array] : this[_mapping_dept];
1940
+ if (isExempt || this[_level] + 1 < activeMappingDept) {
1885
1941
  let ids = [];
1886
1942
  _.each(data, (result) => {
1887
1943
  let value = result[source_property];
@@ -1932,6 +1988,8 @@ export default class SpiceModel {
1932
1988
  skip_cache: this[_skip_cache],
1933
1989
  _level: this[_level] + 1,
1934
1990
  mapping_dept: this[_mapping_dept],
1991
+ mapping_dept_scalar: this[_mapping_dept_scalar],
1992
+ mapping_dept_array: this[_mapping_dept_array],
1935
1993
  mapping_dept_exempt: this[_mapping_dept_exempt],
1936
1994
  _columns: columnArray.join(","),
1937
1995
  _current_path: childPath,
@@ -2096,6 +2154,7 @@ export default class SpiceModel {
2096
2154
  mapping_dept,
2097
2155
  level,
2098
2156
  columns,
2157
+ serializationType,
2099
2158
  ) => {
2100
2159
  // Skip if columns are specified but this field isn't included
2101
2160
  if (!this.isFieldInColumns(i, columns)) {
@@ -2110,7 +2169,7 @@ export default class SpiceModel {
2110
2169
  properties[i].map.destination || i,
2111
2170
  properties[i],
2112
2171
  args,
2113
- type,
2172
+ serializationType,
2114
2173
  mapping_dept,
2115
2174
  level,
2116
2175
  columns,
@@ -2132,6 +2191,7 @@ export default class SpiceModel {
2132
2191
  mapping_dept,
2133
2192
  level,
2134
2193
  columns,
2194
+ serializationType,
2135
2195
  ) => {
2136
2196
  // Skip if columns are specified but this field isn't included
2137
2197
  if (!this.isFieldInColumns(i, columns)) {
@@ -2147,7 +2207,7 @@ export default class SpiceModel {
2147
2207
  properties[i].map.destination || i,
2148
2208
  properties[i],
2149
2209
  args,
2150
- type,
2210
+ serializationType,
2151
2211
  mapping_dept,
2152
2212
  level,
2153
2213
  columns,
@@ -2197,6 +2257,7 @@ export default class SpiceModel {
2197
2257
  this[_mapping_dept],
2198
2258
  this[_level],
2199
2259
  this[_columns],
2260
+ type,
2200
2261
  );
2201
2262
  // Guard against modifiers that return undefined
2202
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: 'item',
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
+ });