spice-js 2.7.17 → 2.7.19

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.
@@ -502,27 +502,29 @@ class SpiceModel {
502
502
  var key = "get::" + _this4.type + "::" + args.id + (args.columns ? "::" + args.columns : "");
503
503
  var results = {};
504
504
 
505
+ // Filter out id, meta().id, and any column ending with .id (e.g., dependents.id)
506
+ if (args.columns) {
507
+ args.columns = args.columns.split(",").map(col => col.trim()).filter(col => col !== "").filter(col => {
508
+ var cleaned = col.replace(/`/g, "").toLowerCase();
509
+ return cleaned !== "id" && cleaned !== "meta().id" && !cleaned.endsWith(".id");
510
+ }).join(",");
511
+ }
512
+
513
+ // Save filtered columns before buildJoinMetadata modifies them
514
+ var originalColumns = args.columns;
515
+
505
516
  // Extract nestings from columns if specified
506
517
  var nestings = _this4.extractNestings(((_args9 = args) == null ? void 0 : _args9.columns) || "", _this4.type);
507
518
 
508
519
  // Build join metadata and prepare columns
509
520
  _this4.buildJoinMetadata(nestings, args);
510
- var columns = args.columns;
521
+
522
+ // Use original (filtered) columns for extracting base column names
523
+ var columns = originalColumns;
511
524
  var columnArray = [];
512
525
  if (columns) {
513
- columns.split(',').forEach(col => {
514
- var parts = col.trim().split("."); // remove the first segment
515
- // Also match if parts[0] is source_property or '`' + source_property + '`'
516
- var firstPart = parts[0];
517
- // Remove backticks if present
518
- var cleanFirstPart = firstPart && firstPart.startsWith('`') && firstPart.endsWith('`') ? firstPart.slice(1, -1) : firstPart;
519
-
520
- //if (parts.length > 0 && (firstPart === source_property || cleanFirstPart === source_property)) {
521
- // Use cleanFirstPart for the map key to handle both cases consistently
522
- // Remove backticks from each column segment, if present, before joining
523
- columnArray.push(parts.slice(1).map(segment => segment && segment.startsWith('`') && segment.endsWith('`') ? segment.slice(1, -1) : segment).join("."));
524
- // }
525
- });
526
+ var rawColumns = columns.split(",").map(col => col.trim()).filter(col => col !== "").map(col => col.replace(/`/g, ""));
527
+ columnArray = [...new Set(_this4.extractBaseColumns(rawColumns).filter(col => col && col !== ""))];
526
528
  }
527
529
  if (_this4.shouldUseCache(_this4.type)) {
528
530
  // Retrieve the cached results
@@ -644,7 +646,7 @@ class SpiceModel {
644
646
  if ((cached_results == null ? void 0 : cached_results.value) === undefined || (yield _this6.shouldForceRefresh(cached_results))) {
645
647
  results = yield _this6.database.multi_get(args.ids, {
646
648
  keep_type: true,
647
- columns: args.columns && args.columns.length > 0 && args.columns != "" ? [..._this6.extractBaseColumns(args.columns), 'type'] : []
649
+ columns: args.columns && args.columns.length > 0 && args.columns != "" ? [..._this6.extractBaseColumns(args.columns), "type"] : []
648
650
  });
649
651
  _this6.getCacheProviderObject(_this6.type).set(key, {
650
652
  value: results,
@@ -654,7 +656,7 @@ class SpiceModel {
654
656
  } else {
655
657
  results = yield _this6.database.multi_get(args.ids, {
656
658
  keep_type: true,
657
- columns: args.columns && args.columns.length > 0 && args.columns != "" ? [..._this6.extractBaseColumns(args.columns), 'type'] : []
659
+ columns: args.columns && args.columns.length > 0 && args.columns != "" ? [..._this6.extractBaseColumns(args.columns), "type"] : []
658
660
  });
659
661
  }
660
662
  }
@@ -955,36 +957,47 @@ class SpiceModel {
955
957
  var col = (raw || "").trim();
956
958
  if (col === "" || col === "meta().id") return undefined;
957
959
  if (/^\s*ARRAY\s+/i.test(col) || /\w+\s*\(/.test(col)) return col;
960
+
961
+ // If the first segment is a mapped model property, always project the
962
+ // root mapped field (supports deep paths like dependents.passports.holder).
963
+ var deepMappedPathMatch = col.match(/^\s*`?(\w+)`?(?:\.`?\w+`?)+(?:\s+AS\s+`?([\w]+)`?)?\s*$/i);
964
+ if (deepMappedPathMatch) {
965
+ var _this$props$alias;
966
+ var alias = deepMappedPathMatch[1];
967
+ if (alias !== this.type && ((_this$props$alias = this.props[alias]) == null ? void 0 : _this$props$alias.map)) {
968
+ return q(this.type) + "." + q(alias);
969
+ }
970
+ }
958
971
  var m = col.match(/^\s*`?(\w+)`?\.`?(\w+)`?(?:\s+AS\s+`?([\w]+)`?)?\s*$/i);
959
972
  if (m) {
960
- var alias = m[1];
973
+ var _alias = m[1];
961
974
  var field = m[2];
962
975
  var explicitAs = m[3];
963
976
 
964
977
  // If alias matches this.type, don't prepend again
965
- if (alias === this.type) {
966
- var _qualified = q(alias) + "." + q(field);
978
+ if (_alias === this.type) {
979
+ var _qualified = q(_alias) + "." + q(field);
967
980
  return explicitAs ? _qualified + " AS " + q(explicitAs) : _qualified;
968
981
  }
969
982
 
970
983
  // Check if alias has a map - if so, simplify to root column only (raw ID)
971
- var prop = this.props[alias];
984
+ var prop = this.props[_alias];
972
985
  if (prop == null ? void 0 : prop.map) {
973
986
  // Return base table qualified root column, ignoring the .field part
974
- return q(this.type) + "." + q(alias);
987
+ return q(this.type) + "." + q(_alias);
975
988
  }
976
- if (arraySet.has(alias)) {
977
- var proj = this.buildArrayProjection(alias, field);
978
- if (explicitAs && explicitAs !== alias + "_" + field) {
989
+ if (arraySet.has(_alias)) {
990
+ var proj = this.buildArrayProjection(_alias, field);
991
+ if (explicitAs && explicitAs !== _alias + "_" + field) {
979
992
  proj = proj.replace(/AS\s+`[^`]+`$/i, "AS " + q(explicitAs));
980
993
  }
981
994
  return proj;
982
995
  }
983
- if (protectedSet.has(alias)) {
984
- var aliased = q(alias) + "." + (field.startsWith("`") ? field : q(field));
996
+ if (protectedSet.has(_alias)) {
997
+ var aliased = q(_alias) + "." + (field.startsWith("`") ? field : q(field));
985
998
  return explicitAs ? aliased + " AS " + q(explicitAs) : aliased;
986
999
  }
987
- var qualified = q(this.type) + "." + q(alias) + "." + q(field);
1000
+ var qualified = q(this.type) + "." + q(_alias) + "." + q(field);
988
1001
  return explicitAs ? qualified + " AS " + q(explicitAs) : qualified;
989
1002
  }
990
1003
  var looksProtected = [...protectedSet].some(a => col === a || col === q(a) || col.startsWith(a + ".") || col.startsWith(q(a) + "."));
@@ -1030,9 +1043,14 @@ class SpiceModel {
1030
1043
  if (aliasMatch) {
1031
1044
  return aliasMatch[1].trim();
1032
1045
  }
1033
- // Otherwise, if a dot is present, take the part after the last dot
1046
+ // Otherwise, for dot-notation, keep the first segment after removing
1047
+ // the base model prefix (e.g. user.group.name -> group).
1034
1048
  if (col.includes(".")) {
1035
- return col.split(".").pop().trim();
1049
+ var pathParts = col.split(".").map(part => part.trim()).filter(part => part !== "");
1050
+ if (pathParts[0] === this.type && pathParts.length > 1) {
1051
+ pathParts = pathParts.slice(1);
1052
+ }
1053
+ return pathParts[0];
1036
1054
  }
1037
1055
  return col;
1038
1056
  });
@@ -1142,7 +1160,7 @@ class SpiceModel {
1142
1160
  // then normalize names and add default aliases
1143
1161
  //console.log("Columns in BuildJoinMetadata", args.columns);
1144
1162
 
1145
- this[_columns] = (_args$columns = args.columns) != null ? _args$columns : '';
1163
+ this[_columns] = (_args$columns = args.columns) != null ? _args$columns : "";
1146
1164
  args.columns = this.prepColumns(args.columns, protectedAliases, arrayAliases);
1147
1165
  args.columns = this.fixColumnName(args.columns, protectedAliases);
1148
1166
  //console.log("Columns in BuildJoinMetadata after fixColumnName", args.columns);
@@ -1237,6 +1255,17 @@ class SpiceModel {
1237
1255
  if (args.mapping_dept) _this10[_mapping_dept] = args.mapping_dept;
1238
1256
  if (args.mapping_dept_exempt) _this10[_mapping_dept_exempt] = args.mapping_dept_exempt;
1239
1257
 
1258
+ // Filter out id, meta().id, and any column ending with .id (e.g., dependents.id)
1259
+ if (args.columns) {
1260
+ args.columns = args.columns.split(",").map(col => col.trim()).filter(col => col !== "").filter(col => {
1261
+ var cleaned = col.replace(/`/g, "").toLowerCase();
1262
+ return cleaned !== "id" && cleaned !== "meta().id" && !cleaned.endsWith(".id");
1263
+ }).join(",");
1264
+ }
1265
+
1266
+ // Save filtered columns before buildJoinMetadata modifies them
1267
+ var originalColumns = args.columns;
1268
+
1240
1269
  // Find alias tokens from query/columns/sort
1241
1270
  var nestings = [..._this10.extractNestings(((_args10 = args) == null ? void 0 : _args10.query) || "", _this10.type),
1242
1271
  //...this.extractNestings(args?.columns || "", this.type),
@@ -1279,14 +1308,14 @@ class SpiceModel {
1279
1308
  var isEmpty = (cached == null ? void 0 : cached.value) === undefined;
1280
1309
  var refresh = yield _this10.shouldForceRefresh(cached);
1281
1310
  if (isEmpty || refresh) {
1282
- results = yield _this10.fetchResults(args, query);
1311
+ results = yield _this10.fetchResults(args, query, mappedNestings);
1283
1312
  _this10.getCacheProviderObject(_this10.type).set(cacheKey, {
1284
1313
  value: results,
1285
1314
  time: new Date().getTime()
1286
1315
  }, _this10.getCacheConfig(_this10.type));
1287
1316
  }
1288
1317
  } else {
1289
- results = yield _this10.fetchResults(args, query);
1318
+ results = yield _this10.fetchResults(args, query, mappedNestings);
1290
1319
  }
1291
1320
 
1292
1321
  // Serializer still handles class-based refs and value_field
@@ -1319,7 +1348,36 @@ class SpiceModel {
1319
1348
  }
1320
1349
  })();
1321
1350
  }
1322
- fetchResults(args, query) {
1351
+ correctColumns(columns, mappedNestings) {
1352
+ // Preserve normal columns and only rewrite mapped alias dot-paths
1353
+ // to the root mapped field on the base resource.
1354
+ if (!columns || typeof columns !== "string") return columns;
1355
+ var mappedAliasSet = new Set((mappedNestings || []).map(m => m.alias));
1356
+ var isMappedAlias = alias => {
1357
+ var _this$props, _this$props$alias2;
1358
+ return !!((_this$props = this.props) == null ? void 0 : (_this$props$alias2 = _this$props[alias]) == null ? void 0 : _this$props$alias2.map);
1359
+ };
1360
+ var normalized = columns.split(",").map(col => col.trim()).filter(col => col !== "").map(col => {
1361
+ var cleanCol = col.replace(/`/g, "").trim();
1362
+ var parts = cleanCol.split(".");
1363
+ var firstSegment = parts[0];
1364
+ var secondSegment = parts[1];
1365
+ var mappedAlias = null;
1366
+ if (isMappedAlias(firstSegment)) {
1367
+ mappedAlias = firstSegment;
1368
+ } else if (firstSegment === this.type && secondSegment && isMappedAlias(secondSegment)) {
1369
+ mappedAlias = secondSegment;
1370
+ } else if (mappedAliasSet.has(firstSegment)) {
1371
+ mappedAlias = firstSegment;
1372
+ }
1373
+ if (mappedAlias) {
1374
+ return "`" + this.type + "`.`" + mappedAlias + "`";
1375
+ }
1376
+ return col;
1377
+ });
1378
+ return _.join(_.uniq(_.compact(normalized)), ",");
1379
+ }
1380
+ fetchResults(args, query, mappedNestings) {
1323
1381
  var _this11 = this;
1324
1382
  return _asyncToGenerator(function* () {
1325
1383
  var _this11$_ctx;
@@ -1332,7 +1390,8 @@ class SpiceModel {
1332
1390
  } else if (args.is_full_text === "true") {
1333
1391
  return yield _this11.database.full_text_search(_this11.type, query || "", args.limit, args.offset, args._join);
1334
1392
  } else {
1335
- var result = yield _this11.database.search(_this11.type, args.columns || "", query || "", args.limit, args.offset, args.sort, args.do_count, args.statement_consistent, args._join);
1393
+ var correctedColumns = _this11.correctColumns(args.columns, mappedNestings);
1394
+ var result = yield _this11.database.search(_this11.type, correctedColumns || args.columns || "", query || "", args.limit, args.offset, args.sort, args.do_count, args.statement_consistent, args._join);
1336
1395
  return result;
1337
1396
  }
1338
1397
  });
@@ -1431,16 +1490,16 @@ class SpiceModel {
1431
1490
  // Create a set of columns where the first segment matches source_property
1432
1491
  var columnArray = [];
1433
1492
  if (columns) {
1434
- columns.split(',').forEach(col => {
1493
+ columns.split(",").forEach(col => {
1435
1494
  var parts = col.trim().split("."); // remove the first segment
1436
1495
  // Also match if parts[0] is source_property or '`' + source_property + '`'
1437
1496
  var firstPart = parts[0];
1438
1497
  // Remove backticks if present
1439
- var cleanFirstPart = firstPart && firstPart.startsWith('`') && firstPart.endsWith('`') ? firstPart.slice(1, -1) : firstPart;
1498
+ var cleanFirstPart = firstPart && firstPart.startsWith("`") && firstPart.endsWith("`") ? firstPart.slice(1, -1) : firstPart;
1440
1499
  if (parts.length > 0 && (firstPart === source_property || cleanFirstPart === source_property)) {
1441
1500
  // Use cleanFirstPart for the map key to handle both cases consistently
1442
1501
  // Remove backticks from each column segment, if present, before joining
1443
- columnArray.push(parts.slice(1).map(segment => segment && segment.startsWith('`') && segment.endsWith('`') ? segment.slice(1, -1) : segment).join("."));
1502
+ columnArray.push(parts.slice(1).map(segment => segment && segment.startsWith("`") && segment.endsWith("`") ? segment.slice(1, -1) : segment).join("."));
1444
1503
  }
1445
1504
  });
1446
1505
  }
@@ -1456,8 +1515,8 @@ class SpiceModel {
1456
1515
  var value = result[source_property];
1457
1516
 
1458
1517
  // Check if value is in the empty string key (column aliasing issue)
1459
- if ((value === undefined || _.isObject(value) && _.isEmpty(value)) && result[''] && result[''][source_property]) {
1460
- value = result[''][source_property];
1518
+ if ((value === undefined || _.isObject(value) && _.isEmpty(value)) && result[""] && result[""][source_property]) {
1519
+ value = result[""][source_property];
1461
1520
  }
1462
1521
  if (_.isString(value) && value != "") {
1463
1522
  // Value is a raw ID string
@@ -1480,7 +1539,7 @@ class SpiceModel {
1480
1539
  _level: _this13[_level] + 1,
1481
1540
  mapping_dept: _this13[_mapping_dept],
1482
1541
  mapping_dept_exempt: _this13[_mapping_dept_exempt],
1483
- _columns: columnArray.join(','),
1542
+ _columns: columnArray.join(","),
1484
1543
  _current_path: childPath
1485
1544
  }));
1486
1545
  return objInstance.getMulti({
@@ -1516,8 +1575,8 @@ class SpiceModel {
1516
1575
  var sourceValue = result[source_property];
1517
1576
 
1518
1577
  // Check if value is in the empty string key (column aliasing issue)
1519
- if ((sourceValue === undefined || _.isObject(sourceValue) && _.isEmpty(sourceValue)) && result[''] && result[''][source_property]) {
1520
- sourceValue = result[''][source_property];
1578
+ if ((sourceValue === undefined || _.isObject(sourceValue) && _.isEmpty(sourceValue)) && result[""] && result[""][source_property]) {
1579
+ sourceValue = result[""][source_property];
1521
1580
  }
1522
1581
 
1523
1582
  // Get the ID to match against - either a string ID or the id property of an object
@@ -1540,16 +1599,16 @@ class SpiceModel {
1540
1599
  var p = (_this14$_ctx = _this14[_ctx]) == null ? void 0 : _this14$_ctx.profiler;
1541
1600
  var columnArray = [];
1542
1601
  if (columns) {
1543
- columns.split(',').forEach(col => {
1602
+ columns.split(",").forEach(col => {
1544
1603
  var parts = col.trim().split("."); // remove the first segment
1545
1604
  // Also match if parts[0] is source_property or '`' + source_property + '`'
1546
1605
  var firstPart = parts[0];
1547
1606
  // Remove backticks if present
1548
- var cleanFirstPart = firstPart && firstPart.startsWith('`') && firstPart.endsWith('`') ? firstPart.slice(1, -1) : firstPart;
1607
+ var cleanFirstPart = firstPart && firstPart.startsWith("`") && firstPart.endsWith("`") ? firstPart.slice(1, -1) : firstPart;
1549
1608
  if (parts.length > 0 && (firstPart === source_property || cleanFirstPart === source_property)) {
1550
1609
  // Use cleanFirstPart for the map key to handle both cases consistently
1551
1610
  // Remove backticks from each column segment, if present, before joining
1552
- columnArray.push(parts.slice(1).map(segment => segment && segment.startsWith('`') && segment.endsWith('`') ? segment.slice(1, -1) : segment).join("."));
1611
+ columnArray.push(parts.slice(1).map(segment => segment && segment.startsWith("`") && segment.endsWith("`") ? segment.slice(1, -1) : segment).join("."));
1553
1612
  }
1554
1613
  });
1555
1614
  }
@@ -1561,12 +1620,16 @@ class SpiceModel {
1561
1620
  if (isExempt || _this14[_level] + 1 < _this14[_mapping_dept]) {
1562
1621
  var ids = [];
1563
1622
  _.each(data, result => {
1564
- var value = [];
1565
- if (_.isArray(result[source_property])) {
1566
- value = result[source_property];
1623
+ var value = result[source_property];
1624
+
1625
+ // Some list/select projections place fields under an empty-string key.
1626
+ if ((value === undefined || _.isObject(value) && _.isEmpty(value)) && result[""] && result[""][source_property] !== undefined) {
1627
+ value = result[""][source_property];
1567
1628
  }
1568
- if (_.isString(result[source_property])) {
1569
- value = [result[source_property]];
1629
+ if (_.isString(value)) {
1630
+ value = [value];
1631
+ } else if (!_.isArray(value)) {
1632
+ value = [];
1570
1633
  }
1571
1634
 
1572
1635
  // Extract IDs - handle both string IDs and objects with id property
@@ -1594,7 +1657,7 @@ class SpiceModel {
1594
1657
  _level: _this14[_level] + 1,
1595
1658
  mapping_dept: _this14[_mapping_dept],
1596
1659
  mapping_dept_exempt: _this14[_mapping_dept_exempt],
1597
- _columns: columnArray.join(','),
1660
+ _columns: columnArray.join(","),
1598
1661
  _current_path: childPath
1599
1662
  })).getMulti({
1600
1663
  skip_hooks: true,
@@ -1619,14 +1682,29 @@ class SpiceModel {
1619
1682
  if (returned_obj.status == "fulfilled") return returned_obj.value;
1620
1683
  })));
1621
1684
  _.each(data, result => {
1685
+ var sourceValue = result[source_property];
1686
+ var hasFallbackSource = result[""] && result[""][source_property] !== undefined;
1622
1687
  if (_.isString(result[store_property])) {
1623
1688
  result[store_property] = [result[store_property]];
1624
1689
  }
1625
- if (!_.has(result, source_property)) {
1690
+ if (!_.has(result, source_property) && !hasFallbackSource) {
1691
+ if (!_.isArray(result[store_property])) {
1692
+ result[store_property] = [];
1693
+ }
1626
1694
  result[source_property] = [];
1627
1695
  return;
1628
1696
  }
1629
- result[store_property] = _.map(result[source_property], item => {
1697
+
1698
+ // Match mapToObject behavior for projected rows that use result[""].
1699
+ if ((sourceValue === undefined || _.isObject(sourceValue) && _.isEmpty(sourceValue)) && hasFallbackSource) {
1700
+ sourceValue = result[""][source_property];
1701
+ }
1702
+ if (_.isString(sourceValue)) {
1703
+ sourceValue = [sourceValue];
1704
+ } else if (!_.isArray(sourceValue)) {
1705
+ sourceValue = [];
1706
+ }
1707
+ result[store_property] = _.map(sourceValue, item => {
1630
1708
  // Get the ID to match - either a string ID or the id property of an object
1631
1709
  var itemId = _.isString(item) ? item : _.isObject(item) ? item.id : null;
1632
1710
  return _.find(returned_objects, p => p.id === itemId);
@@ -1669,7 +1747,7 @@ class SpiceModel {
1669
1747
  extractBaseColumns(columns) {
1670
1748
  if (!columns || !Array.isArray(columns)) return columns;
1671
1749
  return [...new Set(columns.map(col => {
1672
- var firstDot = col.indexOf('.');
1750
+ var firstDot = col.indexOf(".");
1673
1751
  return firstDot > -1 ? col.substring(0, firstDot) : col;
1674
1752
  }))];
1675
1753
  }
@@ -1683,14 +1761,14 @@ class SpiceModel {
1683
1761
  */
1684
1762
  isFieldInColumns(fieldName, columns) {
1685
1763
  // No columns filter = include all
1686
- if (!columns || columns === '') return true;
1687
- var tokens = columns.split(',');
1764
+ if (!columns || columns === "") return true;
1765
+ var tokens = columns.split(",");
1688
1766
  for (var col of tokens) {
1689
1767
  var trimmed = col.trim();
1690
1768
  // Check if this column starts with the field name (exact match or nested like "fieldName.subfield")
1691
- var firstPart = trimmed.split('.')[0];
1769
+ var firstPart = trimmed.split(".")[0];
1692
1770
  // Handle backtick-wrapped column names
1693
- var cleanFirstPart = firstPart.startsWith('`') && firstPart.endsWith('`') ? firstPart.slice(1, -1) : firstPart;
1771
+ var cleanFirstPart = firstPart.startsWith("`") && firstPart.endsWith("`") ? firstPart.slice(1, -1) : firstPart;
1694
1772
  if (cleanFirstPart === fieldName) {
1695
1773
  return true;
1696
1774
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spice-js",
3
- "version": "2.7.17",
3
+ "version": "2.7.19",
4
4
  "description": "spice",
5
5
  "main": "build/index.js",
6
6
  "repository": {