@applicaster/zapp-react-native-ui-components 14.0.0-rc.9 → 15.0.0-rc.1
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/Components/AnimatedInOut/index.tsx +5 -3
- package/Components/AudioPlayer/index.tsx +15 -0
- package/Components/AudioPlayer/mobile/Layout.tsx +66 -0
- package/Components/AudioPlayer/{__tests__/__snapshots__/audioPlayer.test.js.snap → mobile/__tests__/__snapshots__/audioPlayerMobileLayout.test.js.snap} +2 -2
- package/Components/AudioPlayer/mobile/__tests__/audioPlayerMobileLayout.test.js +18 -0
- package/Components/AudioPlayer/mobile/index.tsx +18 -0
- package/Components/AudioPlayer/{Artwork.tsx → tv/Artwork.tsx} +3 -2
- package/Components/AudioPlayer/{Channel.tsx → tv/Channel.tsx} +7 -7
- package/Components/AudioPlayer/tv/Layout.tsx +168 -0
- package/Components/AudioPlayer/{Runtime.tsx → tv/Runtime.tsx} +7 -1
- package/Components/AudioPlayer/{Summary.tsx → tv/Summary.tsx} +6 -2
- package/Components/AudioPlayer/{Title.tsx → tv/Title.tsx} +6 -2
- package/Components/AudioPlayer/{__tests__ → tv/__tests__}/__snapshots__/Runtime.test.js.snap +2 -2
- package/Components/AudioPlayer/tv/__tests__/__snapshots__/audioPlayer.test.js.snap +164 -0
- package/Components/AudioPlayer/tv/__tests__/__snapshots__/channel.test.js.snap +19 -0
- package/Components/AudioPlayer/{__tests__ → tv/__tests__}/__snapshots__/summary.test.js.snap +1 -2
- package/Components/AudioPlayer/{__tests__ → tv/__tests__}/__snapshots__/title.test.js.snap +1 -2
- package/Components/AudioPlayer/{__tests__ → tv/__tests__}/audioPlayer.test.js +7 -3
- package/Components/AudioPlayer/{helpers.tsx → tv/helpers.tsx} +11 -5
- package/Components/AudioPlayer/{AudioPlayer.tsx → tv/index.tsx} +17 -58
- package/Components/AudioPlayer/types.ts +40 -0
- package/Components/BaseFocusable/index.tsx +23 -12
- package/Components/Cell/Cell.tsx +91 -64
- package/Components/Cell/CellWithFocusable.tsx +3 -0
- package/Components/Cell/__tests__/CellWIthFocusable.test.js +3 -2
- package/Components/Cell/index.js +7 -3
- package/Components/ComponentResolver/index.ts +1 -1
- package/Components/FeedLoader/FeedLoader.tsx +7 -16
- package/Components/FeedLoader/FeedLoaderHOC.tsx +21 -0
- package/Components/FeedLoader/index.js +2 -8
- package/Components/Focusable/Focusable.tsx +12 -3
- package/Components/Focusable/FocusableTvOS.tsx +5 -5
- package/Components/Focusable/FocusableiOS.tsx +2 -2
- package/Components/Focusable/Touchable.tsx +5 -3
- package/Components/Focusable/__tests__/index.android.test.tsx +3 -0
- package/Components/Focusable/index.android.tsx +19 -11
- package/Components/Focusable/index.tsx +1 -1
- package/Components/FocusableGroup/FocusableTvOS.tsx +1 -1
- package/Components/FocusableList/FocusableItem.tsx +4 -3
- package/Components/FocusableList/FocusableListItemWrapper.tsx +2 -1
- package/Components/FocusableList/hooks/useCellState.android.ts +13 -3
- package/Components/FocusableList/index.tsx +20 -9
- package/Components/FreezeWithCallback/__tests__/index.test.tsx +67 -43
- package/Components/GeneralContentScreen/utils/__tests__/useCurationAPI.test.js +42 -59
- package/Components/GeneralContentScreen/utils/useCurationAPI.ts +13 -10
- package/Components/HandlePlayable/HandlePlayable.tsx +25 -9
- package/Components/HookRenderer/HookRenderer.tsx +5 -1
- package/Components/Layout/TV/LayoutBackground.tsx +1 -1
- package/Components/Layout/TV/__tests__/index.test.tsx +0 -1
- package/Components/MasterCell/DefaultComponents/ActionButton.tsx +6 -2
- package/Components/MasterCell/DefaultComponents/Button.tsx +1 -1
- package/Components/MasterCell/DefaultComponents/FocusableView/index.tsx +4 -39
- package/Components/MasterCell/DefaultComponents/Image/hoc/withDimensions.tsx +1 -1
- package/Components/MasterCell/DefaultComponents/ImageContainer/index.tsx +1 -1
- package/Components/MasterCell/DefaultComponents/SecondaryImage/Image.tsx +65 -17
- package/Components/MasterCell/DefaultComponents/SecondaryImage/__tests__/Image.test.tsx +21 -3
- package/Components/MasterCell/DefaultComponents/SecondaryImage/__tests__/__snapshots__/Image.test.tsx.snap +6 -3
- package/Components/MasterCell/DefaultComponents/Text/index.tsx +26 -6
- package/Components/MasterCell/DefaultComponents/__tests__/image.test.js +10 -10
- package/Components/MasterCell/DefaultComponents/__tests__/text.test.tsx +18 -18
- package/Components/MasterCell/SharedUI/CollapsibleTextContainer/__tests__/index.test.tsx +10 -10
- package/Components/MasterCell/elementMapper.tsx +1 -2
- package/Components/MasterCell/index.tsx +1 -1
- package/Components/MasterCell/utils/behaviorProvider.ts +82 -14
- package/Components/MasterCell/utils/index.ts +11 -5
- package/Components/OfflineHandler/NotificationView/__tests__/index.test.tsx +13 -18
- package/Components/OfflineHandler/__tests__/__snapshots__/index.test.tsx.snap +9 -0
- package/Components/OfflineHandler/__tests__/index.test.tsx +26 -35
- package/Components/PlayerContainer/ErrorDisplay/index.ts +1 -1
- package/Components/PlayerContainer/PlayerContainer.tsx +46 -33
- package/Components/PlayerContainer/ProgramInfo/index.tsx +1 -1
- package/Components/PlayerContainer/index.ts +1 -1
- package/Components/PlayerImageBackground/index.tsx +1 -1
- package/Components/River/ComponentsMap/ComponentsMap.tsx +0 -1
- package/Components/River/ComponentsMap/hooks/__tests__/useLoadingState.test.ts +378 -0
- package/Components/River/ComponentsMap/hooks/useLoadingState.ts +2 -2
- package/Components/River/RefreshControl.tsx +11 -17
- package/Components/River/RiverItem.tsx +3 -0
- package/Components/River/TV/River.tsx +2 -17
- package/Components/River/TV/index.tsx +3 -1
- package/Components/River/TV/withPipesV1DataLoader.tsx +43 -0
- package/Components/River/TV/withRiverDataLoader.tsx +17 -0
- package/Components/River/TV/withTVEventHandler.tsx +1 -1
- package/Components/River/__tests__/__snapshots__/componentsMap.test.js.snap +2 -0
- package/Components/River/__tests__/river.test.js +12 -26
- package/Components/River/index.tsx +1 -1
- package/Components/Screen/__tests__/Screen.test.tsx +28 -29
- package/Components/Screen/__tests__/navigationHandler.test.ts +133 -22
- package/Components/Screen/navigationHandler.ts +20 -2
- package/Components/ScreenResolver/index.tsx +15 -0
- package/Components/ScreenRevealManager/ScreenRevealManager.ts +76 -0
- package/Components/ScreenRevealManager/__tests__/ScreenRevealManager.test.ts +107 -0
- package/Components/ScreenRevealManager/__tests__/withScreenRevealManager.test.tsx +96 -0
- package/Components/ScreenRevealManager/index.ts +1 -0
- package/Components/ScreenRevealManager/withScreenRevealManager.tsx +79 -0
- package/Components/Tabs/TV/Tabs.android.tsx +1 -3
- package/Components/Tabs/Tabs.tsx +2 -3
- package/Components/TextInputTv/__tests__/__snapshots__/TextInputTv.test.js.snap +13 -0
- package/Components/TextInputTv/index.tsx +11 -0
- package/Components/Touchable/__tests__/__snapshots__/touchable.test.tsx.snap +34 -0
- package/Components/Touchable/__tests__/touchable.test.tsx +12 -17
- package/Components/Transitioner/__tests__/__snapshots__/Scene.test.js.snap +15 -9
- package/Components/VideoLive/animationUtils.ts +3 -3
- package/Components/VideoModal/ModalAnimation/AnimatedScrollModal.tsx +3 -9
- package/Components/VideoModal/ModalAnimation/AnimatedScrollModal.web.tsx +294 -0
- package/Components/VideoModal/ModalAnimation/AnimatedVideoPlayerComponent.web.tsx +93 -0
- package/Components/VideoModal/ModalAnimation/ModalAnimationContext.tsx +73 -29
- package/Components/VideoModal/PlayerDetails.tsx +24 -2
- package/Components/VideoModal/PlayerWrapper.tsx +26 -142
- package/Components/VideoModal/VideoModal.tsx +3 -17
- package/Components/VideoModal/__tests__/PlayerDetails.test.tsx +5 -5
- package/Components/VideoModal/__tests__/PlayerWrapper.test.tsx +1 -7
- package/Components/VideoModal/__tests__/__snapshots__/PlayerWrapper.test.tsx.snap +44 -240
- package/Components/VideoModal/hooks/__tests__/useDelayedPlayerDetails.test.ts +9 -1
- package/Components/VideoModal/hooks/index.ts +0 -2
- package/Components/VideoModal/hooks/useDelayedPlayerDetails.ts +40 -15
- package/Components/VideoModal/hooks/useModalSize.ts +18 -2
- package/Components/VideoModal/hooks/utils/__tests__/showDetails.test.ts +2 -2
- package/Components/VideoModal/hooks/utils/index.ts +4 -0
- package/Components/VideoModal/utils.ts +6 -0
- package/Components/Viewport/ViewportAware/__tests__/viewportAware.test.js +12 -16
- package/Components/Viewport/ViewportTracker/__tests__/viewportTracker.test.js +84 -24
- package/Components/Viewport/VisibilitySensor/VisibilitySensor.tsx +3 -3
- package/Components/default-cell-renderer/viewTrees/tv/DefaultCell/index.ts +3 -3
- package/Contexts/CellFocusedStateContext/index.tsx +27 -0
- package/Contexts/ConfigutaionContext/__tests__/ConfigurationProvider.test.tsx +3 -3
- package/Contexts/ScreenContext/index.tsx +46 -6
- package/Decorators/ConfigurationWrapper/__tests__/withConfigurationProvider.test.tsx +3 -3
- package/Decorators/ConfigurationWrapper/withConfigurationProvider.tsx +2 -2
- package/Decorators/RiverFeedLoader/__tests__/__snapshots__/riverFeedLoader.test.tsx.snap +221 -209
- package/Decorators/RiverFeedLoader/__tests__/riverFeedLoader.test.tsx +14 -16
- package/Decorators/RiverFeedLoader/__tests__/utils.test.ts +0 -20
- package/Decorators/RiverFeedLoader/index.tsx +22 -4
- package/Decorators/RiverFeedLoader/utils/index.ts +0 -18
- package/Decorators/RiverResolver/__tests__/riverResolver.test.tsx +3 -6
- package/Decorators/ZappPipesDataConnector/ResolverSelector.tsx +25 -0
- package/Decorators/ZappPipesDataConnector/__tests__/NullFeedResolver.test.tsx +78 -0
- package/Decorators/ZappPipesDataConnector/__tests__/ResolverSelector.test.tsx +205 -0
- package/Decorators/ZappPipesDataConnector/__tests__/StaticFeedResolver.test.tsx +251 -0
- package/Decorators/ZappPipesDataConnector/__tests__/UrlFeedResolver.test.tsx +368 -0
- package/Decorators/ZappPipesDataConnector/__tests__/utils.test.ts +39 -0
- package/Decorators/ZappPipesDataConnector/index.tsx +26 -293
- package/Decorators/ZappPipesDataConnector/resolvers/NullFeedResolver.tsx +25 -0
- package/Decorators/ZappPipesDataConnector/resolvers/StaticFeedResolver.tsx +87 -0
- package/Decorators/ZappPipesDataConnector/resolvers/UrlFeedResolver.tsx +266 -0
- package/Decorators/ZappPipesDataConnector/types.ts +29 -0
- package/Decorators/ZappPipesDataConnector/utils/mongoFilter.ts +738 -0
- package/Decorators/ZappPipesDataConnector/utils/useFilter.tsx +157 -0
- package/events/index.ts +3 -0
- package/package.json +5 -10
- package/Components/AudioPlayer/AudioPlayerLayout.tsx +0 -202
- package/Components/AudioPlayer/__tests__/__snapshots__/audioPlayerLayout.test.js.snap +0 -66
- package/Components/AudioPlayer/__tests__/__snapshots__/channel.test.js.snap +0 -28
- package/Components/AudioPlayer/__tests__/audioPlayerLayout.test.js +0 -26
- package/Components/AudioPlayer/index.ts +0 -1
- package/Components/River/__tests__/__snapshots__/river.test.js.snap +0 -27
- package/Components/VideoModal/hooks/useBackgroundColor.ts +0 -10
- /package/Components/AudioPlayer/{__tests__ → tv/__tests__}/Runtime.test.js +0 -0
- /package/Components/AudioPlayer/{__tests__ → tv/__tests__}/__snapshots__/artWork.test.js.snap +0 -0
- /package/Components/AudioPlayer/{__tests__ → tv/__tests__}/artWork.test.js +0 -0
- /package/Components/AudioPlayer/{__tests__ → tv/__tests__}/channel.test.js +0 -0
- /package/Components/AudioPlayer/{__tests__ → tv/__tests__}/summary.test.js +0 -0
- /package/Components/AudioPlayer/{__tests__ → tv/__tests__}/title.test.js +0 -0
|
@@ -0,0 +1,738 @@
|
|
|
1
|
+
import { createLogger } from "@applicaster/zapp-react-native-utils/logger";
|
|
2
|
+
|
|
3
|
+
const { log_error } = createLogger({
|
|
4
|
+
subsystem: "General",
|
|
5
|
+
category: "MongoSyntaxFilter",
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
// types.ts (or at the top of your file)
|
|
9
|
+
type JsonPrimitive = string | number | boolean | null;
|
|
10
|
+
type JsonValue = JsonPrimitive | JsonObject | JsonArray;
|
|
11
|
+
interface JsonObject {
|
|
12
|
+
[key: string]: JsonValue;
|
|
13
|
+
}
|
|
14
|
+
interface JsonArray extends Array<JsonValue> {}
|
|
15
|
+
|
|
16
|
+
type MongoQuery = {
|
|
17
|
+
[key: string]: any; // Values can be direct matches or operator objects
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
// --- Helper Functions ---
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Retrieves a value from an object using a dot-notation path.
|
|
24
|
+
* @param obj The object to traverse.
|
|
25
|
+
* @param path The dot-notation path (e.g., "a.b.c").
|
|
26
|
+
* @returns The value at the specified path, or undefined if the path doesn't exist.
|
|
27
|
+
*/
|
|
28
|
+
function getValueByPath(obj: JsonObject, path: string): any {
|
|
29
|
+
if (path === "" || path === undefined) return undefined;
|
|
30
|
+
const keys = path.split(".");
|
|
31
|
+
let current: any = obj;
|
|
32
|
+
|
|
33
|
+
for (const key of keys) {
|
|
34
|
+
if (
|
|
35
|
+
current &&
|
|
36
|
+
typeof current === "object" &&
|
|
37
|
+
Object.prototype.hasOwnProperty.call(current, key)
|
|
38
|
+
) {
|
|
39
|
+
current = (current as JsonObject)[key];
|
|
40
|
+
} else {
|
|
41
|
+
return undefined; // Path does not fully resolve or encounters non-object/missing key
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return current;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Checks if a dot-notation path exists within an object.
|
|
50
|
+
* @param obj The object to check.
|
|
51
|
+
* @param path The dot-notation path.
|
|
52
|
+
* @returns True if the path exists, false otherwise.
|
|
53
|
+
*/
|
|
54
|
+
function pathExists(obj: JsonObject, path: string): boolean {
|
|
55
|
+
if (path === "" || path === undefined) return false;
|
|
56
|
+
const keys = path.split(".");
|
|
57
|
+
let current: any = obj;
|
|
58
|
+
|
|
59
|
+
for (let i = 0; i < keys.length; i++) {
|
|
60
|
+
const key = keys[i];
|
|
61
|
+
|
|
62
|
+
if (
|
|
63
|
+
typeof current === "object" &&
|
|
64
|
+
current !== null &&
|
|
65
|
+
Object.prototype.hasOwnProperty.call(current, key)
|
|
66
|
+
) {
|
|
67
|
+
if (i === keys.length - 1) {
|
|
68
|
+
return true; // Full path exists
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
current = (current as JsonObject)[key];
|
|
72
|
+
} else {
|
|
73
|
+
return false; // Path does not exist
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return false; // Should be covered by loop logic
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Performs a basic deep equality check between two values.
|
|
82
|
+
* Handles primitives, arrays, and objects.
|
|
83
|
+
* @param val1 First value.
|
|
84
|
+
* @param val2 Second value.
|
|
85
|
+
* @returns True if values are deeply equal, false otherwise.
|
|
86
|
+
*/
|
|
87
|
+
function isEqual(val1: any, val2: any): boolean {
|
|
88
|
+
if (val1 === val2) return true; // Handles primitives and same object/array references
|
|
89
|
+
|
|
90
|
+
if (typeof val1 !== typeof val2) return false;
|
|
91
|
+
if (val1 === null || val2 === null) return false; // Already covered by val1 === val2 if both are null
|
|
92
|
+
|
|
93
|
+
if (typeof val1 === "object") {
|
|
94
|
+
if (Array.isArray(val1)) {
|
|
95
|
+
if (!Array.isArray(val2) || val1.length !== val2.length) return false;
|
|
96
|
+
|
|
97
|
+
for (let i = 0; i < val1.length; i++) {
|
|
98
|
+
if (!isEqual(val1[i], val2[i])) return false;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return true;
|
|
102
|
+
} else {
|
|
103
|
+
// Both are objects
|
|
104
|
+
if (Array.isArray(val2)) return false;
|
|
105
|
+
const keys1 = Object.keys(val1);
|
|
106
|
+
const keys2 = Object.keys(val2);
|
|
107
|
+
if (keys1.length !== keys2.length) return false;
|
|
108
|
+
|
|
109
|
+
for (const key of keys1) {
|
|
110
|
+
if (
|
|
111
|
+
!Object.prototype.hasOwnProperty.call(val2, key) ||
|
|
112
|
+
!isEqual(val1[key], val2[key])
|
|
113
|
+
) {
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return true;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return false; // Mismatch for other types or conditions
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Evaluates if a value matches a specific condition object (e.g., { $gt: 10, $lt: 20 }).
|
|
127
|
+
* This is used by the $not operator.
|
|
128
|
+
* @param value The actual value from the document field.
|
|
129
|
+
* @param conditionObject The condition object (e.g., { $gt: 10 }).
|
|
130
|
+
* @returns True if the value satisfies all conditions in conditionObject.
|
|
131
|
+
*/
|
|
132
|
+
function checkValueAgainstOperatorObject(
|
|
133
|
+
value: any,
|
|
134
|
+
conditionObject: JsonObject
|
|
135
|
+
): boolean {
|
|
136
|
+
for (const operator in conditionObject) {
|
|
137
|
+
if (Object.prototype.hasOwnProperty.call(conditionObject, operator)) {
|
|
138
|
+
const operand = conditionObject[operator];
|
|
139
|
+
|
|
140
|
+
// This is a simplified version of evaluateFieldCondition's operator switch,
|
|
141
|
+
// applied directly to 'value'. $exists is not applicable here.
|
|
142
|
+
switch (operator) {
|
|
143
|
+
case "$eq":
|
|
144
|
+
if (!isEqual(value, operand)) return false;
|
|
145
|
+
break;
|
|
146
|
+
case "$ne":
|
|
147
|
+
if (isEqual(value, operand)) return false;
|
|
148
|
+
break;
|
|
149
|
+
case "$gt":
|
|
150
|
+
if (value === undefined || value === null || !(value > operand)) {
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
break;
|
|
155
|
+
case "$gte":
|
|
156
|
+
if (value === undefined || value === null || !(value >= operand)) {
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
break;
|
|
161
|
+
case "$lt":
|
|
162
|
+
if (value === undefined || value === null || !(value < operand)) {
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
break;
|
|
167
|
+
case "$lte":
|
|
168
|
+
if (value === undefined || value === null || !(value <= operand)) {
|
|
169
|
+
return false;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
break;
|
|
173
|
+
case "$in":
|
|
174
|
+
if (!Array.isArray(operand)) {
|
|
175
|
+
throw new Error("$in requires an array operand.");
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (Array.isArray(value)) {
|
|
179
|
+
// If field value is an array
|
|
180
|
+
if (!value.some((item) => operand.includes(item))) return false;
|
|
181
|
+
} else {
|
|
182
|
+
if (!operand.includes(value)) return false;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
break;
|
|
186
|
+
case "$nin":
|
|
187
|
+
if (!Array.isArray(operand)) {
|
|
188
|
+
throw new Error("$nin requires an array operand.");
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (Array.isArray(value)) {
|
|
192
|
+
// If field value is an array
|
|
193
|
+
if (value.some((item) => operand.includes(item))) return false;
|
|
194
|
+
} else {
|
|
195
|
+
if (operand.includes(value)) return false;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
break;
|
|
199
|
+
// Regex and other relevant operators can be added here if needed for $not's operand
|
|
200
|
+
default:
|
|
201
|
+
log_error(
|
|
202
|
+
`Unsupported operator '${operator}' inside $not's operand object.`
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
return false;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return true; // All conditions in conditionObject passed for 'value'
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Checks if a given object matches a MongoDB-style query.
|
|
215
|
+
* @param obj The object to check.
|
|
216
|
+
* @param query The MongoDB-style query.
|
|
217
|
+
* @returns True if the object matches the query, false otherwise.
|
|
218
|
+
*/
|
|
219
|
+
function matchesQuery(obj: JsonObject, query: MongoQuery): boolean {
|
|
220
|
+
for (const key in query) {
|
|
221
|
+
if (Object.prototype.hasOwnProperty.call(query, key)) {
|
|
222
|
+
const condition = query[key];
|
|
223
|
+
|
|
224
|
+
if (key.startsWith("$")) {
|
|
225
|
+
// Top-level logical operators ($and, $or, $not, $nor)
|
|
226
|
+
switch (key) {
|
|
227
|
+
case "$and":
|
|
228
|
+
if (!Array.isArray(condition)) {
|
|
229
|
+
throw new Error(
|
|
230
|
+
"$and operator requires an array of query objects."
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (
|
|
235
|
+
!condition.every((subQuery: MongoQuery) =>
|
|
236
|
+
matchesQuery(obj, subQuery)
|
|
237
|
+
)
|
|
238
|
+
) {
|
|
239
|
+
return false;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
break;
|
|
243
|
+
case "$or":
|
|
244
|
+
if (!Array.isArray(condition)) {
|
|
245
|
+
throw new Error(
|
|
246
|
+
"$or operator requires an array of query objects."
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (
|
|
251
|
+
!condition.some((subQuery: MongoQuery) =>
|
|
252
|
+
matchesQuery(obj, subQuery)
|
|
253
|
+
)
|
|
254
|
+
) {
|
|
255
|
+
return false;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
break;
|
|
259
|
+
case "$not": // Top-level $not
|
|
260
|
+
if (typeof condition !== "object" || condition === null) {
|
|
261
|
+
throw new Error("Top-level $not requires a query object.");
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (matchesQuery(obj, condition as MongoQuery)) {
|
|
265
|
+
return false; // If inner query matches, $not fails
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
break;
|
|
269
|
+
case "$nor":
|
|
270
|
+
if (!Array.isArray(condition)) {
|
|
271
|
+
throw new Error(
|
|
272
|
+
"$nor operator requires an array of query objects."
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if (
|
|
277
|
+
condition.some((subQuery: MongoQuery) =>
|
|
278
|
+
matchesQuery(obj, subQuery)
|
|
279
|
+
)
|
|
280
|
+
) {
|
|
281
|
+
return false; // If any subQuery matches, $nor fails
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
break;
|
|
285
|
+
default:
|
|
286
|
+
log_error(`Unsupported top-level logical operator: ${key}`);
|
|
287
|
+
|
|
288
|
+
return false;
|
|
289
|
+
}
|
|
290
|
+
} else {
|
|
291
|
+
// Field-specific query
|
|
292
|
+
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
|
293
|
+
if (!evaluateFieldCondition(obj, key, condition)) {
|
|
294
|
+
return false;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
return true; // All conditions in the query passed
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Evaluates if a document's field satisfies a given condition.
|
|
305
|
+
* @param obj The document being checked.
|
|
306
|
+
* @param fieldPath The path to the field in the document (e.g., "age" or "address.city").
|
|
307
|
+
* @param fieldCondition The condition for the field (e.g., 30, { $gte: 30 }, /pattern/).
|
|
308
|
+
* @returns True if the field satisfies the condition, false otherwise.
|
|
309
|
+
*/
|
|
310
|
+
function evaluateFieldCondition(
|
|
311
|
+
obj: JsonObject,
|
|
312
|
+
fieldPath: string,
|
|
313
|
+
fieldCondition: any
|
|
314
|
+
): boolean {
|
|
315
|
+
const actualValue = getValueByPath(obj, fieldPath);
|
|
316
|
+
const fieldActuallyExists = pathExists(obj, fieldPath);
|
|
317
|
+
|
|
318
|
+
// Case 1: fieldCondition is an operator object (e.g., { $gt: 10, $lt: 20 })
|
|
319
|
+
if (
|
|
320
|
+
typeof fieldCondition === "object" &&
|
|
321
|
+
fieldCondition !== null &&
|
|
322
|
+
!Array.isArray(fieldCondition) &&
|
|
323
|
+
!(fieldCondition instanceof RegExp)
|
|
324
|
+
) {
|
|
325
|
+
const operatorKeys = Object.keys(fieldCondition).filter((k) =>
|
|
326
|
+
k.startsWith("$")
|
|
327
|
+
);
|
|
328
|
+
|
|
329
|
+
if (operatorKeys.length > 0) {
|
|
330
|
+
for (const operator in fieldCondition) {
|
|
331
|
+
if (!Object.prototype.hasOwnProperty.call(fieldCondition, operator)) {
|
|
332
|
+
continue;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
if (
|
|
336
|
+
operator === "$options" &&
|
|
337
|
+
Object.prototype.hasOwnProperty.call(fieldCondition, "$regex")
|
|
338
|
+
) {
|
|
339
|
+
continue;
|
|
340
|
+
} // Handled by $regex
|
|
341
|
+
|
|
342
|
+
const operand = fieldCondition[operator];
|
|
343
|
+
|
|
344
|
+
switch (operator) {
|
|
345
|
+
case "$eq":
|
|
346
|
+
if (operand === null) {
|
|
347
|
+
// MongoDB's special null equality
|
|
348
|
+
if (actualValue !== null && fieldActuallyExists) return false; // Matches if value is null or field doesn't exist
|
|
349
|
+
} else {
|
|
350
|
+
if (!isEqual(actualValue, operand)) return false;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
break;
|
|
354
|
+
case "$ne":
|
|
355
|
+
// For $ne, null is treated literally. { field: { $ne: null } } means field must exist and not be null.
|
|
356
|
+
if (operand === null) {
|
|
357
|
+
if (actualValue === null || !fieldActuallyExists) return false;
|
|
358
|
+
} else {
|
|
359
|
+
if (isEqual(actualValue, operand)) return false;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
break;
|
|
363
|
+
case "$gt":
|
|
364
|
+
if (
|
|
365
|
+
actualValue === undefined ||
|
|
366
|
+
actualValue === null ||
|
|
367
|
+
!(actualValue > operand)
|
|
368
|
+
) {
|
|
369
|
+
return false;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
break;
|
|
373
|
+
case "$gte":
|
|
374
|
+
if (
|
|
375
|
+
actualValue === undefined ||
|
|
376
|
+
actualValue === null ||
|
|
377
|
+
!(actualValue >= operand)
|
|
378
|
+
) {
|
|
379
|
+
return false;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
break;
|
|
383
|
+
case "$lt":
|
|
384
|
+
if (
|
|
385
|
+
actualValue === undefined ||
|
|
386
|
+
actualValue === null ||
|
|
387
|
+
!(actualValue < operand)
|
|
388
|
+
) {
|
|
389
|
+
return false;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
break;
|
|
393
|
+
case "$lte":
|
|
394
|
+
if (
|
|
395
|
+
actualValue === undefined ||
|
|
396
|
+
actualValue === null ||
|
|
397
|
+
!(actualValue <= operand)
|
|
398
|
+
) {
|
|
399
|
+
return false;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
break;
|
|
403
|
+
case "$in":
|
|
404
|
+
if (!Array.isArray(operand)) {
|
|
405
|
+
throw new Error("$in operator requires an array operand.");
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
if (!fieldActuallyExists) {
|
|
409
|
+
// Field does not exist
|
|
410
|
+
if (!operand.includes(null)) return false; // Matches if null is in operand array
|
|
411
|
+
} else if (Array.isArray(actualValue)) {
|
|
412
|
+
// Field is an array
|
|
413
|
+
if (!actualValue.some((item) => operand.includes(item))) {
|
|
414
|
+
return false;
|
|
415
|
+
}
|
|
416
|
+
} else {
|
|
417
|
+
// Field is a single value
|
|
418
|
+
if (!operand.includes(actualValue)) return false;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
break;
|
|
422
|
+
case "$nin":
|
|
423
|
+
if (!Array.isArray(operand)) {
|
|
424
|
+
throw new Error("$nin operator requires an array operand.");
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
if (!fieldActuallyExists) {
|
|
428
|
+
// Field does not exist
|
|
429
|
+
if (operand.includes(null)) return false; // Does NOT match if null is in operand array
|
|
430
|
+
} else if (Array.isArray(actualValue)) {
|
|
431
|
+
// Field is an array
|
|
432
|
+
if (actualValue.some((item) => operand.includes(item))) {
|
|
433
|
+
return false;
|
|
434
|
+
}
|
|
435
|
+
} else {
|
|
436
|
+
// Field is a single value
|
|
437
|
+
if (operand.includes(actualValue)) return false;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
break;
|
|
441
|
+
case "$exists":
|
|
442
|
+
if (typeof operand !== "boolean") {
|
|
443
|
+
throw new Error("$exists operator requires a boolean operand.");
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
if (operand === true && !fieldActuallyExists) return false;
|
|
447
|
+
if (operand === false && fieldActuallyExists) return false;
|
|
448
|
+
break;
|
|
449
|
+
|
|
450
|
+
case "$type": {
|
|
451
|
+
if (typeof operand !== "string") {
|
|
452
|
+
throw new Error("$type requires a string operand.");
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
const jsType = typeof actualValue;
|
|
456
|
+
|
|
457
|
+
let matchesType = false;
|
|
458
|
+
|
|
459
|
+
if (
|
|
460
|
+
!fieldActuallyExists &&
|
|
461
|
+
operand !==
|
|
462
|
+
"null" /* and other types that could match non-existence if we were super strict */
|
|
463
|
+
) {
|
|
464
|
+
// Generally, $type checks existing fields. If field doesn't exist, it won't match most types.
|
|
465
|
+
// Exception: $type: "null" could match if we consider missing field as null for $type.
|
|
466
|
+
// MongoDB: $type matches BSON types. For non-existent fields, it depends.
|
|
467
|
+
// For simplicity, if field doesn't exist, it only matches $type: "null" if actualValue is undefined and operand is "null" (which is not how it works)
|
|
468
|
+
// Let's be strict: if field doesn't exist, it doesn't have a type other than "undefined" (which isn't a Mongo type string).
|
|
469
|
+
// If operand is 'null', it checks if actualValue is null. If field is missing, actualValue is undefined.
|
|
470
|
+
if (operand === "null") {
|
|
471
|
+
// $type: 'null'
|
|
472
|
+
matchesType = actualValue === null; // Only if value is literally null
|
|
473
|
+
} else {
|
|
474
|
+
matchesType = false;
|
|
475
|
+
}
|
|
476
|
+
} else {
|
|
477
|
+
switch (operand) {
|
|
478
|
+
case "string":
|
|
479
|
+
matchesType = jsType === "string";
|
|
480
|
+
break;
|
|
481
|
+
case "number":
|
|
482
|
+
matchesType =
|
|
483
|
+
jsType === "number" &&
|
|
484
|
+
Number.isFinite(actualValue as number);
|
|
485
|
+
|
|
486
|
+
break;
|
|
487
|
+
case "boolean":
|
|
488
|
+
matchesType = jsType === "boolean";
|
|
489
|
+
break;
|
|
490
|
+
case "object":
|
|
491
|
+
matchesType =
|
|
492
|
+
jsType === "object" &&
|
|
493
|
+
actualValue !== null &&
|
|
494
|
+
!Array.isArray(actualValue);
|
|
495
|
+
|
|
496
|
+
break;
|
|
497
|
+
case "array":
|
|
498
|
+
matchesType = Array.isArray(actualValue);
|
|
499
|
+
break;
|
|
500
|
+
case "null":
|
|
501
|
+
matchesType = actualValue === null;
|
|
502
|
+
break;
|
|
503
|
+
default:
|
|
504
|
+
throw new Error(
|
|
505
|
+
`Unsupported type string for $type: ${operand}`
|
|
506
|
+
);
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
if (!matchesType) return false;
|
|
511
|
+
break;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
case "$all": {
|
|
515
|
+
if (!Array.isArray(operand)) {
|
|
516
|
+
throw new Error("$all requires an array operand.");
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
if (!fieldActuallyExists || !Array.isArray(actualValue)) {
|
|
520
|
+
return false;
|
|
521
|
+
} // Field must exist and be an array
|
|
522
|
+
|
|
523
|
+
// Assumes operand contains literal values for $all
|
|
524
|
+
const allPresent = operand.every((itemInOperand) =>
|
|
525
|
+
actualValue.some((valInArray) =>
|
|
526
|
+
isEqual(valInArray, itemInOperand)
|
|
527
|
+
)
|
|
528
|
+
);
|
|
529
|
+
|
|
530
|
+
if (!allPresent) return false;
|
|
531
|
+
break;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
case "$elemMatch": {
|
|
535
|
+
if (typeof operand !== "object" || operand === null) {
|
|
536
|
+
throw new Error("$elemMatch requires a query object.");
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
if (!fieldActuallyExists || !Array.isArray(actualValue)) {
|
|
540
|
+
return false;
|
|
541
|
+
} // Field must exist and be an array
|
|
542
|
+
|
|
543
|
+
const foundElemMatch = actualValue.some((element) => {
|
|
544
|
+
if (typeof element !== "object" || element === null) return false; // Elements must be objects for sub-query
|
|
545
|
+
|
|
546
|
+
return matchesQuery(element as JsonObject, operand as MongoQuery);
|
|
547
|
+
});
|
|
548
|
+
|
|
549
|
+
if (!foundElemMatch) return false;
|
|
550
|
+
break;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
case "$size":
|
|
554
|
+
if (
|
|
555
|
+
typeof operand !== "number" ||
|
|
556
|
+
!Number.isInteger(operand) ||
|
|
557
|
+
operand < 0
|
|
558
|
+
) {
|
|
559
|
+
throw new Error("$size requires a non-negative integer.");
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
if (!fieldActuallyExists || !Array.isArray(actualValue)) {
|
|
563
|
+
return false;
|
|
564
|
+
} // Field must exist and be an array
|
|
565
|
+
|
|
566
|
+
if (actualValue.length !== operand) return false;
|
|
567
|
+
break;
|
|
568
|
+
|
|
569
|
+
case "$regex": {
|
|
570
|
+
const pattern = operand;
|
|
571
|
+
const options = fieldCondition.$options || "";
|
|
572
|
+
let regexInstance: RegExp;
|
|
573
|
+
|
|
574
|
+
if (pattern instanceof RegExp) {
|
|
575
|
+
regexInstance = new RegExp(
|
|
576
|
+
pattern.source,
|
|
577
|
+
options || pattern.flags
|
|
578
|
+
);
|
|
579
|
+
} else if (typeof pattern === "string") {
|
|
580
|
+
regexInstance = new RegExp(pattern, options);
|
|
581
|
+
} else {
|
|
582
|
+
throw new Error(
|
|
583
|
+
"$regex pattern must be a string or RegExp instance."
|
|
584
|
+
);
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
if (
|
|
588
|
+
typeof actualValue !== "string" ||
|
|
589
|
+
!regexInstance.test(actualValue)
|
|
590
|
+
) {
|
|
591
|
+
return false;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
break;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
case "$not":
|
|
598
|
+
// Operand for $not is an operator expression object (e.g., { $gt: 10 }) or a RegExp
|
|
599
|
+
if (operand instanceof RegExp) {
|
|
600
|
+
if (
|
|
601
|
+
typeof actualValue === "string" &&
|
|
602
|
+
operand.test(actualValue)
|
|
603
|
+
) {
|
|
604
|
+
return false;
|
|
605
|
+
} // If regex matches, $not fails
|
|
606
|
+
} else if (typeof operand === "object" && operand !== null) {
|
|
607
|
+
if (
|
|
608
|
+
checkValueAgainstOperatorObject(
|
|
609
|
+
actualValue,
|
|
610
|
+
operand as JsonObject
|
|
611
|
+
)
|
|
612
|
+
) {
|
|
613
|
+
return false;
|
|
614
|
+
} // If inner condition matches, $not fails
|
|
615
|
+
} else {
|
|
616
|
+
throw new Error(
|
|
617
|
+
"$not requires an operator expression or RegExp."
|
|
618
|
+
);
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
break;
|
|
622
|
+
default:
|
|
623
|
+
log_error(`Unsupported field operator: ${operator}`);
|
|
624
|
+
|
|
625
|
+
return false;
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
return true; // All operator conditions for this field passed
|
|
630
|
+
} else {
|
|
631
|
+
// Case 2: fieldCondition is an object for sub-document matching (e.g., { subField: "value" })
|
|
632
|
+
if (typeof actualValue !== "object" || actualValue === null) return false; // actualValue must be an object for sub-query
|
|
633
|
+
|
|
634
|
+
return matchesQuery(
|
|
635
|
+
actualValue as JsonObject,
|
|
636
|
+
fieldCondition as MongoQuery
|
|
637
|
+
);
|
|
638
|
+
}
|
|
639
|
+
} else {
|
|
640
|
+
// Case 3: fieldCondition is a primitive, RegExp, or null (implicit $eq)
|
|
641
|
+
if (fieldCondition instanceof RegExp) {
|
|
642
|
+
return (
|
|
643
|
+
typeof actualValue === "string" && fieldCondition.test(actualValue)
|
|
644
|
+
);
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
// MongoDB's special null equality for implicit { field: null }
|
|
648
|
+
if (fieldCondition === null) {
|
|
649
|
+
return actualValue === null || !fieldActuallyExists; // Matches if value is null or field doesn't exist
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
return isEqual(actualValue, fieldCondition);
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
// --- Main Exported Function ---
|
|
657
|
+
|
|
658
|
+
/**
|
|
659
|
+
* Filters an array of JSON objects based on a MongoDB-style query filter.
|
|
660
|
+
* @param data Array of JSON objects to filter.
|
|
661
|
+
* @param query The MongoDB-style query filter object.
|
|
662
|
+
* @returns A new array containing only the objects that match the query.
|
|
663
|
+
*
|
|
664
|
+
* @example
|
|
665
|
+
* const data = [
|
|
666
|
+
* { name: "Alice", age: 30, city: "New York", tags: ["dev", "js"] },
|
|
667
|
+
* { name: "Bob", age: 24, city: "London", tags: ["dev", "python"] },
|
|
668
|
+
* { name: "Charlie", age: 30, city: "Paris", tags: ["qa", "js"] },
|
|
669
|
+
* { name: "David", age: null, city: "Berlin" }
|
|
670
|
+
* ];
|
|
671
|
+
*
|
|
672
|
+
* // Find users older than 25
|
|
673
|
+
* filterObjects(data, { age: { $gt: 25 } });
|
|
674
|
+
* // Result: Alice, Charlie
|
|
675
|
+
*
|
|
676
|
+
* // Find users in New York or London
|
|
677
|
+
* filterObjects(data, { $or: [{ city: "New York" }, { city: "London" }] });
|
|
678
|
+
* // Result: Alice, Bob
|
|
679
|
+
*
|
|
680
|
+
* // Find users with the "dev" tag
|
|
681
|
+
* filterObjects(data, { tags: "dev" }); // or { tags: { $in: ["dev"] } } or { tags: { $all: ["dev"] } }
|
|
682
|
+
* // Result: Alice, Bob
|
|
683
|
+
*
|
|
684
|
+
* // Find users where age is null or missing
|
|
685
|
+
* filterObjects(data, { age: null });
|
|
686
|
+
* // Result: David
|
|
687
|
+
*/
|
|
688
|
+
export function filterObjects(
|
|
689
|
+
data: JsonObject[],
|
|
690
|
+
query: MongoQuery
|
|
691
|
+
): JsonObject[] {
|
|
692
|
+
if (!data || data.length === 0) return [];
|
|
693
|
+
if (Object.keys(query).length === 0) return [...data]; // Return all if query is empty
|
|
694
|
+
|
|
695
|
+
return data.filter((obj) => matchesQuery(obj, query));
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
// Example Usage (can be removed or commented out in final library code)
|
|
699
|
+
/*
|
|
700
|
+
const sampleData: JsonObject[] = [
|
|
701
|
+
{ _id: 1, item: { name: "ab", code: "123" }, qty: 15, tags: ["A", "B", "C"], scores: [ { s: 10, c:5 }, {s: 7, c: 8} ], status: "Active" },
|
|
702
|
+
{ _id: 2, item: { name: "cd", code: "123" }, qty: 20, tags: ["B"], scores: [ { s: 8, c:5 } ], status: "Inactive" },
|
|
703
|
+
{ _id: 3, item: { name: "ij", code: "456" }, qty: 25, tags: ["A", "B"], scores: [], status: "Active", meta: null },
|
|
704
|
+
{ _id: 4, item: { name: "xy", code: "456" }, qty: 30, tags: ["B", "C"], status: "Pending" },
|
|
705
|
+
{ _id: 5, item: { name: "mn", code: "000" }, qty: 20, tags: [["A", "B"], "C"] , meta: { info: "details" }, size: { w: 10, h: 5, uom: "cm"} },
|
|
706
|
+
{ _id: 6, item: { name: "ab", code: "123" }, qty: 10, tags: ["A"], description: "A blue pen." },
|
|
707
|
+
{ _id: 7, item: { name: "zz", code: "789" }, qty: null, tags: ["D"] }, // qty is null
|
|
708
|
+
{ _id: 8, item: { name: "yy", code: "000" } , tags: ["E"], status: "Active"} // qty is missing
|
|
709
|
+
];
|
|
710
|
+
|
|
711
|
+
console.log("--- Test Cases ---");
|
|
712
|
+
|
|
713
|
+
console.log("1. Equality (qty: 15):", filterObjects(sampleData, { qty: 15 }));
|
|
714
|
+
console.log("2. Comparison (qty > 20):", filterObjects(sampleData, { qty: { $gt: 20 } }));
|
|
715
|
+
console.log("3. $in (tags: 'A' or 'D'):", filterObjects(sampleData, { tags: { $in: ["A", "D"] } }));
|
|
716
|
+
console.log("4. $all (tags: 'A' and 'B'):", filterObjects(sampleData, { tags: { $all: ["A", "B"] } }));
|
|
717
|
+
console.log("5. $exists (description exists):", filterObjects(sampleData, { description: { $exists: true } }));
|
|
718
|
+
console.log("6. $exists (description does not exist):", filterObjects(sampleData, { description: { $exists: false } }));
|
|
719
|
+
console.log("7. Nested field (item.name: 'ab'):", filterObjects(sampleData, { "item.name": "ab" }));
|
|
720
|
+
console.log("8. $and (status: 'Active', qty > 10):", filterObjects(sampleData, { $and: [{ status: "Active" }, { qty: { $gt: 10 } }] }));
|
|
721
|
+
console.log("9. $or (qty < 15 or item.code: '456'):", filterObjects(sampleData, { $or: [{ qty: { $lt: 15 } }, { "item.code": "456" }] }));
|
|
722
|
+
console.log("10. Regex (description contains 'pen'):", filterObjects(sampleData, { description: /pen/i }));
|
|
723
|
+
console.log("11. $regex (description starts with 'A'):", filterObjects(sampleData, { description: { $regex: "^A", $options: "i" } }));
|
|
724
|
+
console.log("12. Sub-document match (item: { name: 'ab', code: '123' }):", filterObjects(sampleData, { item: { name: "ab", code: "123" } }));
|
|
725
|
+
console.log("13. $elemMatch (scores has s >= 8):", filterObjects(sampleData, { scores: { $elemMatch: { s: { $gte: 8 } } } }));
|
|
726
|
+
console.log("14. $size (tags array size 1):", filterObjects(sampleData, { tags: { $size: 1 } }));
|
|
727
|
+
console.log("15. $not field level (qty not > 20):", filterObjects(sampleData, { qty: { $not: { $gt: 20 } } }));
|
|
728
|
+
console.log("16. Top-level $not (not status Active):", filterObjects(sampleData, { $not: { status: "Active" } }));
|
|
729
|
+
console.log("17. $type (qty is number):", filterObjects(sampleData, { qty: { $type: "number" } }));
|
|
730
|
+
console.log("18. $type (meta is object):", filterObjects(sampleData, { meta: { $type: "object" } }));
|
|
731
|
+
console.log("19. $type (tags is array):", filterObjects(sampleData, { tags: { $type: "array" } }));
|
|
732
|
+
console.log("20. Query for null value (qty: null - matches null or missing):", filterObjects(sampleData, { qty: null }));
|
|
733
|
+
console.log("21. Query for $ne null (qty: { $ne: null } - matches existing and not null):", filterObjects(sampleData, { qty: { $ne: null } }));
|
|
734
|
+
console.log("22. Query for $in including null (qty: { $in: [15, null] }):", filterObjects(sampleData, { qty: { $in: [15, null] } }));
|
|
735
|
+
console.log("23. Query for $nin including null (qty: { $nin: [20, null] }):", filterObjects(sampleData, { qty: { $nin: [20, null] } }));
|
|
736
|
+
console.log("24. Empty query:", filterObjects(sampleData, {}));
|
|
737
|
+
console.log("25. $nor (neither status Active nor qty < 20):", filterObjects(sampleData, { $nor: [{status: "Active"}, {qty: {$lt: 20}}]}));
|
|
738
|
+
*/
|