Skip to content

Commit 8695556

Browse files
kriskclaude
andcommitted
feat(search): support keyless string entries in logical queries
Allows mixing key-specific and keyless patterns in $and/$or queries: { $and: [{ title: "old" }, "Scalzi"] } Keyless strings search across all indexed keys, similar to a regular string query but composable with logical operators. Closes #736 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 0a9aabf commit 8695556

14 files changed

Lines changed: 215 additions & 40 deletions

dist/fuse.basic.cjs

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1007,6 +1007,17 @@ function parse(query, options) {
10071007
_ref3$auto = _ref3.auto,
10081008
auto = _ref3$auto === void 0 ? true : _ref3$auto;
10091009
var next = function next(query) {
1010+
// Keyless string entry: search across all keys
1011+
if (isString(query)) {
1012+
var obj = {
1013+
keyId: null,
1014+
pattern: query
1015+
};
1016+
if (auto) {
1017+
obj.searcher = createSearcher(query, options);
1018+
}
1019+
return obj;
1020+
}
10101021
var keys = Object.keys(query);
10111022
var isQueryPath = isPath(query);
10121023
if (!isQueryPath && keys.length > 1 && !isExpression(query)) {
@@ -1018,14 +1029,14 @@ function parse(query, options) {
10181029
if (!isString(pattern)) {
10191030
throw new Error(LOGICAL_SEARCH_INVALID_QUERY_FOR_KEY(key));
10201031
}
1021-
var obj = {
1032+
var _obj = {
10221033
keyId: createKeyId(key),
10231034
pattern: pattern
10241035
};
10251036
if (auto) {
1026-
obj.searcher = createSearcher(pattern, options);
1037+
_obj.searcher = createSearcher(pattern, options);
10271038
}
1028-
return obj;
1039+
return _obj;
10291040
}
10301041
var node = {
10311042
children: [],

dist/fuse.basic.js

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1011,6 +1011,17 @@
10111011
_ref3$auto = _ref3.auto,
10121012
auto = _ref3$auto === void 0 ? true : _ref3$auto;
10131013
var next = function next(query) {
1014+
// Keyless string entry: search across all keys
1015+
if (isString(query)) {
1016+
var obj = {
1017+
keyId: null,
1018+
pattern: query
1019+
};
1020+
if (auto) {
1021+
obj.searcher = createSearcher(query, options);
1022+
}
1023+
return obj;
1024+
}
10141025
var keys = Object.keys(query);
10151026
var isQueryPath = isPath(query);
10161027
if (!isQueryPath && keys.length > 1 && !isExpression(query)) {
@@ -1022,14 +1033,14 @@
10221033
if (!isString(pattern)) {
10231034
throw new Error(LOGICAL_SEARCH_INVALID_QUERY_FOR_KEY(key));
10241035
}
1025-
var obj = {
1036+
var _obj = {
10261037
keyId: createKeyId(key),
10271038
pattern: pattern
10281039
};
10291040
if (auto) {
1030-
obj.searcher = createSearcher(pattern, options);
1041+
_obj.searcher = createSearcher(pattern, options);
10311042
}
1032-
return obj;
1043+
return _obj;
10331044
}
10341045
var node = {
10351046
children: [],

dist/fuse.basic.mjs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -803,6 +803,17 @@ function parse(query, options, {
803803
auto = true
804804
} = {}) {
805805
const next = query => {
806+
// Keyless string entry: search across all keys
807+
if (isString(query)) {
808+
const obj = {
809+
keyId: null,
810+
pattern: query
811+
};
812+
if (auto) {
813+
obj.searcher = createSearcher(query, options);
814+
}
815+
return obj;
816+
}
806817
const keys = Object.keys(query);
807818
const isQueryPath = isPath(query);
808819
if (!isQueryPath && keys.length > 1 && !isExpression(query)) {

dist/fuse.cjs

Lines changed: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1639,6 +1639,17 @@ function parse(query, options) {
16391639
_ref3$auto = _ref3.auto,
16401640
auto = _ref3$auto === void 0 ? true : _ref3$auto;
16411641
var next = function next(query) {
1642+
// Keyless string entry: search across all keys
1643+
if (isString(query)) {
1644+
var obj = {
1645+
keyId: null,
1646+
pattern: query
1647+
};
1648+
if (auto) {
1649+
obj.searcher = createSearcher(query, options);
1650+
}
1651+
return obj;
1652+
}
16421653
var keys = Object.keys(query);
16431654
var isQueryPath = isPath(query);
16441655
if (!isQueryPath && keys.length > 1 && !isExpression(query)) {
@@ -1650,14 +1661,14 @@ function parse(query, options) {
16501661
if (!isString(pattern)) {
16511662
throw new Error(LOGICAL_SEARCH_INVALID_QUERY_FOR_KEY(key));
16521663
}
1653-
var obj = {
1664+
var _obj = {
16541665
keyId: createKeyId(key),
16551666
pattern: pattern
16561667
};
16571668
if (auto) {
1658-
obj.searcher = createSearcher(pattern, options);
1669+
_obj.searcher = createSearcher(pattern, options);
16591670
}
1660-
return obj;
1671+
return _obj;
16611672
}
16621673
var node = {
16631674
children: [],
@@ -2027,11 +2038,25 @@ var Fuse = /*#__PURE__*/function () {
20272038
if (!node.children) {
20282039
var keyId = node.keyId,
20292040
searcher = node.searcher;
2030-
var matches = _this._findMatches({
2031-
key: _this._keyStore.get(keyId),
2032-
value: _this._myIndex.getValueForItemAtKeyId(item, keyId),
2033-
searcher: searcher
2034-
});
2041+
var matches;
2042+
if (keyId === null) {
2043+
// Keyless entry: search across all keys
2044+
matches = [];
2045+
_this._myIndex.keys.forEach(function (key, keyIndex) {
2046+
var _matches;
2047+
(_matches = matches).push.apply(_matches, _toConsumableArray(_this._findMatches({
2048+
key: key,
2049+
value: item[keyIndex],
2050+
searcher: searcher
2051+
})));
2052+
});
2053+
} else {
2054+
matches = _this._findMatches({
2055+
key: _this._keyStore.get(keyId),
2056+
value: _this._myIndex.getValueForItemAtKeyId(item, keyId),
2057+
searcher: searcher
2058+
});
2059+
}
20352060
if (matches && matches.length) {
20362061
return [{
20372062
idx: idx,
@@ -2072,9 +2097,9 @@ var Fuse = /*#__PURE__*/function () {
20722097
results.push(resultMap.get(idx));
20732098
}
20742099
expResults.forEach(function (_ref5) {
2075-
var _matches;
2100+
var _matches2;
20762101
var matches = _ref5.matches;
2077-
(_matches = resultMap.get(idx).matches).push.apply(_matches, _toConsumableArray(matches));
2102+
(_matches2 = resultMap.get(idx).matches).push.apply(_matches2, _toConsumableArray(matches));
20782103
});
20792104
}
20802105
}

dist/fuse.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ interface FuseResult<T> {
158158
score?: number;
159159
matches?: ReadonlyArray<FuseResultMatch>;
160160
}
161-
type Expression = {
161+
type Expression = string | {
162162
[key: string]: string;
163163
} | {
164164
$path: ReadonlyArray<string>;

dist/fuse.js

Lines changed: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1643,6 +1643,17 @@
16431643
_ref3$auto = _ref3.auto,
16441644
auto = _ref3$auto === void 0 ? true : _ref3$auto;
16451645
var next = function next(query) {
1646+
// Keyless string entry: search across all keys
1647+
if (isString(query)) {
1648+
var obj = {
1649+
keyId: null,
1650+
pattern: query
1651+
};
1652+
if (auto) {
1653+
obj.searcher = createSearcher(query, options);
1654+
}
1655+
return obj;
1656+
}
16461657
var keys = Object.keys(query);
16471658
var isQueryPath = isPath(query);
16481659
if (!isQueryPath && keys.length > 1 && !isExpression(query)) {
@@ -1654,14 +1665,14 @@
16541665
if (!isString(pattern)) {
16551666
throw new Error(LOGICAL_SEARCH_INVALID_QUERY_FOR_KEY(key));
16561667
}
1657-
var obj = {
1668+
var _obj = {
16581669
keyId: createKeyId(key),
16591670
pattern: pattern
16601671
};
16611672
if (auto) {
1662-
obj.searcher = createSearcher(pattern, options);
1673+
_obj.searcher = createSearcher(pattern, options);
16631674
}
1664-
return obj;
1675+
return _obj;
16651676
}
16661677
var node = {
16671678
children: [],
@@ -2031,11 +2042,25 @@
20312042
if (!node.children) {
20322043
var keyId = node.keyId,
20332044
searcher = node.searcher;
2034-
var matches = _this._findMatches({
2035-
key: _this._keyStore.get(keyId),
2036-
value: _this._myIndex.getValueForItemAtKeyId(item, keyId),
2037-
searcher: searcher
2038-
});
2045+
var matches;
2046+
if (keyId === null) {
2047+
// Keyless entry: search across all keys
2048+
matches = [];
2049+
_this._myIndex.keys.forEach(function (key, keyIndex) {
2050+
var _matches;
2051+
(_matches = matches).push.apply(_matches, _toConsumableArray(_this._findMatches({
2052+
key: key,
2053+
value: item[keyIndex],
2054+
searcher: searcher
2055+
})));
2056+
});
2057+
} else {
2058+
matches = _this._findMatches({
2059+
key: _this._keyStore.get(keyId),
2060+
value: _this._myIndex.getValueForItemAtKeyId(item, keyId),
2061+
searcher: searcher
2062+
});
2063+
}
20392064
if (matches && matches.length) {
20402065
return [{
20412066
idx: idx,
@@ -2076,9 +2101,9 @@
20762101
results.push(resultMap.get(idx));
20772102
}
20782103
expResults.forEach(function (_ref5) {
2079-
var _matches;
2104+
var _matches2;
20802105
var matches = _ref5.matches;
2081-
(_matches = resultMap.get(idx).matches).push.apply(_matches, _toConsumableArray(matches));
2106+
(_matches2 = resultMap.get(idx).matches).push.apply(_matches2, _toConsumableArray(matches));
20822107
});
20832108
}
20842109
}

dist/fuse.min.cjs

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

dist/fuse.min.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/fuse.min.mjs

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

dist/fuse.mjs

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1267,6 +1267,17 @@ function parse(query, options, {
12671267
auto = true
12681268
} = {}) {
12691269
const next = query => {
1270+
// Keyless string entry: search across all keys
1271+
if (isString(query)) {
1272+
const obj = {
1273+
keyId: null,
1274+
pattern: query
1275+
};
1276+
if (auto) {
1277+
obj.searcher = createSearcher(query, options);
1278+
}
1279+
return obj;
1280+
}
12701281
const keys = Object.keys(query);
12711282
const isQueryPath = isPath(query);
12721283
if (!isQueryPath && keys.length > 1 && !isExpression(query)) {
@@ -1628,11 +1639,24 @@ class Fuse {
16281639
keyId,
16291640
searcher
16301641
} = node;
1631-
const matches = this._findMatches({
1632-
key: this._keyStore.get(keyId),
1633-
value: this._myIndex.getValueForItemAtKeyId(item, keyId),
1634-
searcher
1635-
});
1642+
let matches;
1643+
if (keyId === null) {
1644+
// Keyless entry: search across all keys
1645+
matches = [];
1646+
this._myIndex.keys.forEach((key, keyIndex) => {
1647+
matches.push(...this._findMatches({
1648+
key,
1649+
value: item[keyIndex],
1650+
searcher
1651+
}));
1652+
});
1653+
} else {
1654+
matches = this._findMatches({
1655+
key: this._keyStore.get(keyId),
1656+
value: this._myIndex.getValueForItemAtKeyId(item, keyId),
1657+
searcher
1658+
});
1659+
}
16361660
if (matches && matches.length) {
16371661
return [{
16381662
idx,

0 commit comments

Comments
 (0)