@@ -50,32 +50,68 @@ class Query {
50
50
* @returns {Array<Model> } The models that match the query.
51
51
*/
52
52
execute ( model , index ) {
53
- const matchIs = ( query ) => query ?. $is !== undefined ;
54
- const matchPrimitive = ( query ) => [ 'string' , 'number' , 'boolean' ] . includes ( typeof query ) ;
55
- const matchContains = ( query ) => query ?. $contains !== undefined ;
53
+ return Object . values ( index )
54
+ . filter ( m =>
55
+ this . _splitQuery ( this . query )
56
+ . map ( query => Boolean ( this . _matchesQuery ( m , query ) ) )
57
+ . every ( c => c ) ,
58
+ )
59
+ . map ( m => model . fromData ( m ) ) ;
60
+ }
56
61
57
- const matchesQuery = ( subject , inputQuery = this . query ) => {
58
- if ( matchPrimitive ( inputQuery ) ) return subject === inputQuery ;
62
+ /**
63
+ * Recursively checks if a subject matches a given query.
64
+ *
65
+ * This function supports matching:
66
+ * - Primitive values directly (`string`, `number`, `boolean`)
67
+ * - The `$is` property for exact matches
68
+ * - The `$contains` property for substring or array element matches
69
+ *
70
+ * @private
71
+ * @param {* } subject - The subject to be matched.
72
+ * @param {Object } [inputQuery=this.query] - The query to match against. Defaults to `this.query` if not provided.
73
+ * @returns {boolean } True if the subject matches the query, otherwise false.
74
+ */
75
+ _matchesQuery ( subject , inputQuery = this . query ) {
76
+ if ( [ 'string' , 'number' , 'boolean' ] . includes ( typeof inputQuery ) ) return subject === inputQuery ;
59
77
60
- if ( matchIs ( inputQuery ) && subject === inputQuery . $is ) return true ;
78
+ if ( inputQuery ?. $is !== undefined && subject === inputQuery . $is ) return true ;
61
79
62
- if ( matchContains ( inputQuery ) ) {
63
- if ( subject . includes ?. ( inputQuery . $contains ) ) return true ;
80
+ if ( inputQuery ?. $contains !== undefined ) {
81
+ if ( subject . includes ?. ( inputQuery . $contains ) ) return true ;
64
82
65
- for ( const value of subject ) {
66
- if ( matchesQuery ( value , inputQuery . $contains ) ) return true ;
67
- }
83
+ for ( const value of subject ) {
84
+ if ( this . _matchesQuery ( value , inputQuery . $contains ) ) return true ;
68
85
}
86
+ }
69
87
70
- for ( const key of Object . keys ( inputQuery ) ) {
71
- if ( ! [ '$is' , '$contains' ] . includes ( key ) )
72
- if ( matchesQuery ( subject [ key ] , inputQuery [ key ] ) ) return true ;
73
- }
74
- } ;
88
+ for ( const key of Object . keys ( inputQuery ) ) {
89
+ if ( ! [ '$is' , '$contains' ] . includes ( key ) )
90
+ if ( this . _matchesQuery ( subject [ key ] , inputQuery [ key ] ) ) return true ;
91
+ }
75
92
76
- return Object . values ( index )
77
- . filter ( m => matchesQuery ( m ) )
78
- . map ( m => model . fromData ( m ) ) ;
93
+ return false ;
94
+ } ;
95
+
96
+ /**
97
+ * Recursively splits an object into an array of objects,
98
+ * where each key-value pair from the input query becomes a separate object.
99
+ *
100
+ * If the value of a key is a nested object (and not an array),
101
+ * the function recursively splits it, preserving the parent key.
102
+ *
103
+ * @private
104
+ * @param {Object } query - The input object to be split into individual key-value pairs.
105
+ * @returns {Array<Object> } An array of objects, where each object contains a single key-value pair
106
+ * from the original query or its nested objects.
107
+ */
108
+ _splitQuery ( query ) {
109
+ return Object . entries ( query )
110
+ . flatMap ( ( [ key , value ] ) =>
111
+ typeof value === 'object' && value !== null && ! Array . isArray ( value )
112
+ ? this . _splitQuery ( value ) . map ( nestedObj => ( { [ key ] : nestedObj } ) )
113
+ : { [ key ] : value } ,
114
+ ) ;
79
115
}
80
116
}
81
117
0 commit comments