@thi.ng/oquery 2.1.46 → 2.2.0
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/CHANGELOG.md +16 -1
- package/README.md +26 -19
- package/api.d.ts +37 -7
- package/index.d.ts +1 -0
- package/index.js +1 -0
- package/match.d.ts +120 -0
- package/match.js +169 -0
- package/package.json +8 -4
- package/query.d.ts +19 -1
- package/query.js +37 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Change Log
|
|
2
2
|
|
|
3
|
-
- **Last updated**: 2023-
|
|
3
|
+
- **Last updated**: 2023-09-15T12:33:37Z
|
|
4
4
|
- **Generator**: [thi.ng/monopub](https://thi.ng/monopub)
|
|
5
5
|
|
|
6
6
|
All notable changes to this project will be documented in this file.
|
|
@@ -9,6 +9,21 @@ See [Conventional Commits](https://conventionalcommits.org/) for commit guidelin
|
|
|
9
9
|
**Note:** Unlisted _patch_ versions only involve non-code or otherwise excluded changes
|
|
10
10
|
and/or version bumps of transitive dependencies.
|
|
11
11
|
|
|
12
|
+
## [2.2.0](https://github.com/thi-ng/umbrella/tree/@thi.ng/oquery@2.2.0) (2023-09-15)
|
|
13
|
+
|
|
14
|
+
#### 🚀 Features
|
|
15
|
+
|
|
16
|
+
- add multi-term query() function ([e35519a](https://github.com/thi-ng/umbrella/commit/e35519a))
|
|
17
|
+
- add/update types
|
|
18
|
+
- add matchers for use w/ query() ([20e1baf](https://github.com/thi-ng/umbrella/commit/20e1baf))
|
|
19
|
+
- add matchStrings(), matchPattern(), matchCompare()
|
|
20
|
+
- add tests
|
|
21
|
+
- add matchMultiple(), refactor ([90a7f0b](https://github.com/thi-ng/umbrella/commit/90a7f0b))
|
|
22
|
+
- add/extract matchMultiple()
|
|
23
|
+
- add MatchMultipleOpts()
|
|
24
|
+
- refactor matchStrings() as syntax sugar
|
|
25
|
+
- update tests
|
|
26
|
+
|
|
12
27
|
## [2.1.0](https://github.com/thi-ng/umbrella/tree/@thi.ng/oquery@2.1.0) (2021-11-17)
|
|
13
28
|
|
|
14
29
|
#### 🚀 Features
|
package/README.md
CHANGED
|
@@ -25,9 +25,14 @@ This project is part of the
|
|
|
25
25
|
|
|
26
26
|
## About
|
|
27
27
|
|
|
28
|
-
Datalog-inspired, optimized pattern/predicate query engine for JS objects & arrays.
|
|
28
|
+
Datalog-inspired, optimized pattern/predicate query engine for JS objects & arrays of objects.
|
|
29
29
|
|
|
30
|
-
This
|
|
30
|
+
**IMPORTANT: This README is currently somewhat out-of-date and does not yet
|
|
31
|
+
cover new important features introduced with version 2.2.0 onwards, please
|
|
32
|
+
consult API docs (and/or source code) to view newly added functions and their
|
|
33
|
+
usage...**
|
|
34
|
+
|
|
35
|
+
This package provides a higher-order function `defQuery()`, which takes a
|
|
31
36
|
number of options to configure query behavior and returns an actual query
|
|
32
37
|
function. This returned function can then be used for pattern matching of
|
|
33
38
|
objects and arrays of objects.
|
|
@@ -43,7 +48,6 @@ objects and arrays of objects.
|
|
|
43
48
|
Some of the below features are already partially addressed by other
|
|
44
49
|
thi.ng/umbrella packages, but would benefit from a more unified approach.
|
|
45
50
|
|
|
46
|
-
- [ ] query joins (AND queries)
|
|
47
51
|
- [ ] optional queries (OR queries)
|
|
48
52
|
- [ ] result projection
|
|
49
53
|
- [ ] result aggregation/grouping
|
|
@@ -75,12 +79,13 @@ For Node.js REPL:
|
|
|
75
79
|
const oquery = await import("@thi.ng/oquery");
|
|
76
80
|
```
|
|
77
81
|
|
|
78
|
-
Package sizes (brotli'd, pre-treeshake): ESM: 1.
|
|
82
|
+
Package sizes (brotli'd, pre-treeshake): ESM: 1.66 KB
|
|
79
83
|
|
|
80
84
|
## Dependencies
|
|
81
85
|
|
|
82
86
|
- [@thi.ng/api](https://github.com/thi-ng/umbrella/tree/develop/packages/api)
|
|
83
87
|
- [@thi.ng/checks](https://github.com/thi-ng/umbrella/tree/develop/packages/checks)
|
|
88
|
+
- [@thi.ng/compare](https://github.com/thi-ng/umbrella/tree/develop/packages/compare)
|
|
84
89
|
- [@thi.ng/defmulti](https://github.com/thi-ng/umbrella/tree/develop/packages/defmulti)
|
|
85
90
|
- [@thi.ng/equiv](https://github.com/thi-ng/umbrella/tree/develop/packages/equiv)
|
|
86
91
|
|
|
@@ -152,9 +157,9 @@ import { defQuery } from "@thi.ng/oquery";
|
|
|
152
157
|
|
|
153
158
|
// create query w/ custom options
|
|
154
159
|
// (options explained further below...)
|
|
155
|
-
const
|
|
160
|
+
const q = defQuery({ partial: true });
|
|
156
161
|
|
|
157
|
-
console.log(
|
|
162
|
+
console.log(q(DB, null, "knows", "bob"));
|
|
158
163
|
// {
|
|
159
164
|
// alice: { knows: [ 'bob' ] },
|
|
160
165
|
// charlie: { knows: [ 'bob' ] },
|
|
@@ -186,9 +191,9 @@ Further variations:
|
|
|
186
191
|
key value(s) are matched, using the same logic for the other two terms as in the
|
|
187
192
|
table above.
|
|
188
193
|
|
|
189
|
-
```ts
|
|
194
|
+
```ts tangle:export/readme.ts
|
|
190
195
|
// Who does Alice know?
|
|
191
|
-
|
|
196
|
+
q(DB, "alice", "knows", null)
|
|
192
197
|
// { alice: { knows: [ 'bob', 'charlie', 'dori' ] } }
|
|
193
198
|
```
|
|
194
199
|
|
|
@@ -196,9 +201,9 @@ query(DB, "alice", "knows", null)
|
|
|
196
201
|
predicate will be matched (again using same rules as above for the other query
|
|
197
202
|
terms).
|
|
198
203
|
|
|
199
|
-
```ts
|
|
204
|
+
```ts tangle:export/readme.ts
|
|
200
205
|
// Anyone with initial "A" knows Charlie?
|
|
201
|
-
|
|
206
|
+
q(DB, (s) => s[0] === "a", "knows", "charlie")
|
|
202
207
|
// { alice: { knows: [ 'charlie' ] } }
|
|
203
208
|
```
|
|
204
209
|
|
|
@@ -206,14 +211,16 @@ query(DB, (s) => s[0] === "a", "knows", "charlie")
|
|
|
206
211
|
this case, only predicate-object patterns are used (**no subject terms**, aka
|
|
207
212
|
array indices in this case).
|
|
208
213
|
|
|
209
|
-
```ts
|
|
210
|
-
|
|
214
|
+
```ts tangle:export/readme.ts
|
|
215
|
+
type Person = { id: string; knows: string[] };
|
|
216
|
+
|
|
217
|
+
const DBALT: Person[] = [
|
|
211
218
|
{ id: "alice", knows: ["bob", "charlie"] },
|
|
212
219
|
{ id: "bob", knows: ["alice"] },
|
|
213
220
|
{ id: "charlie", knows: ["alice","bob","dori"] },
|
|
214
221
|
];
|
|
215
222
|
|
|
216
|
-
defQuery()(DBALT, "knows", "alice")
|
|
223
|
+
defQuery<Person[]>()(DBALT, "knows", "alice")
|
|
217
224
|
// [
|
|
218
225
|
// { id: 'bob', knows: [ 'alice' ] },
|
|
219
226
|
// { id: 'charlie', knows: [ 'alice', 'bob', 'dori' ] }
|
|
@@ -225,22 +232,22 @@ defQuery()(DBALT, "knows", "alice")
|
|
|
225
232
|
The following example is using the `DB` object defined [further
|
|
226
233
|
above](#query-patterns)...
|
|
227
234
|
|
|
228
|
-
```ts
|
|
235
|
+
```ts tangle:export/readme2.ts
|
|
229
236
|
import { defQuery } from "@thi.ng/oquery";
|
|
230
237
|
|
|
231
238
|
// using partial result objects option for brevity here
|
|
232
|
-
const
|
|
239
|
+
const q = defQuery({ partial: true });
|
|
233
240
|
|
|
234
241
|
// find all subjects with `type = "person"` relationship
|
|
235
|
-
|
|
242
|
+
q(DB, null, "type", "person");
|
|
236
243
|
// { alice: { type: 'person' }, bob: { type: 'person' } }
|
|
237
244
|
|
|
238
245
|
// everyone w/ given min age
|
|
239
|
-
|
|
246
|
+
q(DB, null, "age", (age) => age >= 33)
|
|
240
247
|
// { alice: { age: 33 } }
|
|
241
248
|
|
|
242
249
|
// select only subjects with A/B initials
|
|
243
|
-
|
|
250
|
+
q(DB, (id) => id >= "a" && id < "c", null, null)
|
|
244
251
|
// {
|
|
245
252
|
// alice: { age: 33, knows: [ 'bob', 'charlie', 'dori' ], type: 'person' },
|
|
246
253
|
// bob: { age: 32, knows: [ 'alice' ], type: 'person', spouse: 'alice' }
|
|
@@ -249,7 +256,7 @@ query(DB, (id) => id >= "a" && id < "c", null, null)
|
|
|
249
256
|
|
|
250
257
|
Union vs. intersection queries:
|
|
251
258
|
|
|
252
|
-
```ts
|
|
259
|
+
```ts tangle:export/readme2.ts
|
|
253
260
|
const union = defQuery();
|
|
254
261
|
|
|
255
262
|
// who knows bob OR charlie?
|
package/api.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import type { Fn6,
|
|
1
|
+
import type { Fn, Fn6, FnU, Keys, NumOrString, Predicate, Predicate2 } from "@thi.ng/api";
|
|
2
2
|
export type FTerm = Predicate<any>;
|
|
3
3
|
export type OTerm = any | null;
|
|
4
4
|
export type SPTerm = Predicate<string> | NumOrString | null;
|
|
5
5
|
export type SPInputTerm = SPTerm | NumOrString[] | Set<NumOrString>;
|
|
6
|
-
export type QueryObj =
|
|
6
|
+
export type QueryObj = Record<string, any>;
|
|
7
7
|
/**
|
|
8
8
|
* All 27 possible query types.
|
|
9
9
|
*
|
|
@@ -33,7 +33,7 @@ export type QueryImpls = Record<QueryType, QueryImpl>;
|
|
|
33
33
|
* If `res` is provided, results will be injected in that object. Otherwise
|
|
34
34
|
* a new result object will be created.
|
|
35
35
|
*/
|
|
36
|
-
export type ObjQueryFn<T extends QueryObj> = (obj: T, s: SPInputTerm, p: SPInputTerm, o: OTerm, res?: QueryObj) => QueryObj;
|
|
36
|
+
export type ObjQueryFn<T extends QueryObj = QueryObj> = (obj: T, s: SPInputTerm, p: SPInputTerm, o: OTerm, res?: QueryObj) => QueryObj;
|
|
37
37
|
/**
|
|
38
38
|
* Takes a source array of objects with this structure: [{p1: o, p2: ...},
|
|
39
39
|
* ...]`, and matches each item using provided `p`(redicate) and `o`bject terms.
|
|
@@ -43,7 +43,7 @@ export type ObjQueryFn<T extends QueryObj> = (obj: T, s: SPInputTerm, p: SPInput
|
|
|
43
43
|
* If `res` is provided, results will be appended to that array. Otherwise a new
|
|
44
44
|
* result array will be created.
|
|
45
45
|
*/
|
|
46
|
-
export type ArrayQueryFn<T extends QueryObj[]> = (src: T, p: SPInputTerm, o: OTerm, res?:
|
|
46
|
+
export type ArrayQueryFn<T extends QueryObj[]> = (src: T, p: SPInputTerm, o: OTerm, res?: T) => T;
|
|
47
47
|
/**
|
|
48
48
|
* Similar to {@link ObjQueryFn}, but only collects and returns a set of
|
|
49
49
|
* matching `s` keys.
|
|
@@ -108,10 +108,10 @@ export interface QueryOpts {
|
|
|
108
108
|
*/
|
|
109
109
|
cwise: boolean;
|
|
110
110
|
/**
|
|
111
|
-
* Only used if `cwise` is enabled. If
|
|
111
|
+
* Only used if `cwise` is enabled. If true, ALL of the query elements must
|
|
112
|
+
* match (aka intersection query). If false (default), an array or Set
|
|
112
113
|
* query term in O(bject) position will succeed if at least ONE of its
|
|
113
|
-
* elements is matched (aka union query).
|
|
114
|
-
* must matched (aka intersection query).
|
|
114
|
+
* elements is matched (aka union query). .
|
|
115
115
|
*
|
|
116
116
|
* @defaultValue false
|
|
117
117
|
*/
|
|
@@ -128,4 +128,34 @@ export interface QueryOpts {
|
|
|
128
128
|
*/
|
|
129
129
|
export interface KeyQueryOpts extends Pick<QueryOpts, "cwise" | "intersect" | "equiv"> {
|
|
130
130
|
}
|
|
131
|
+
export interface QueryTerm<T extends QueryObj = QueryObj> {
|
|
132
|
+
/**
|
|
133
|
+
* Actual query expressed as tuple of `[key, value]` tuple. Predicate
|
|
134
|
+
* functions can be used in either position.
|
|
135
|
+
*/
|
|
136
|
+
q: [Keys<T> | Predicate<string> | null, any];
|
|
137
|
+
/**
|
|
138
|
+
* Optional function to post-process query results.
|
|
139
|
+
*/
|
|
140
|
+
post?: FnU<T[]>;
|
|
141
|
+
/**
|
|
142
|
+
* Query options for this term.
|
|
143
|
+
*/
|
|
144
|
+
opts?: Partial<QueryOpts>;
|
|
145
|
+
}
|
|
146
|
+
export interface MultiQueryOpts<T extends QueryObj = QueryObj> {
|
|
147
|
+
/**
|
|
148
|
+
* Max number of query results. Default: unlimited
|
|
149
|
+
*/
|
|
150
|
+
limit: number;
|
|
151
|
+
/**
|
|
152
|
+
* If given, results are sorted by this key.
|
|
153
|
+
*/
|
|
154
|
+
sort: Keys<T> | Fn<T, any>;
|
|
155
|
+
/**
|
|
156
|
+
* Only used if {@link MultiQueryOpts.sort} is given. If true, reverses sort
|
|
157
|
+
* order.
|
|
158
|
+
*/
|
|
159
|
+
reverse: boolean;
|
|
160
|
+
}
|
|
131
161
|
//# sourceMappingURL=api.d.ts.map
|
package/index.d.ts
CHANGED
package/index.js
CHANGED
package/match.d.ts
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import type { Fn, NumOrString, Predicate } from "@thi.ng/api";
|
|
2
|
+
import { type Operator } from "@thi.ng/compare";
|
|
3
|
+
import type { QueryObj, QueryOpts, QueryTerm } from "./api.js";
|
|
4
|
+
export interface MatchMultipleOpts<T> {
|
|
5
|
+
/**
|
|
6
|
+
* If true, performs union query. I.e. only one of the provided matches
|
|
7
|
+
* needs to succeed.
|
|
8
|
+
*/
|
|
9
|
+
union: boolean;
|
|
10
|
+
/**
|
|
11
|
+
* Transformation function to extract an array of values to be matched from
|
|
12
|
+
* a query item.
|
|
13
|
+
*/
|
|
14
|
+
value: Fn<any, T[]>;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Syntax sugar for {@link matchMultiple} using string arrays. Matches set of
|
|
18
|
+
* given strings against an item's chosen field of strings and by default only
|
|
19
|
+
* succeeds if all provided strings can be matched.
|
|
20
|
+
*
|
|
21
|
+
* @remarks
|
|
22
|
+
* Any provided string prefixed with `!` is treated as an exclusion and any item
|
|
23
|
+
* which contains any of these exclusions is automatically rejected, even if
|
|
24
|
+
* other strings could be matched.
|
|
25
|
+
*
|
|
26
|
+
* See {@link matchMultiple} for more details & code example.
|
|
27
|
+
*
|
|
28
|
+
* @param key
|
|
29
|
+
* @param matches
|
|
30
|
+
* @param opts
|
|
31
|
+
* @returns
|
|
32
|
+
*/
|
|
33
|
+
export declare const matchStrings: <T extends QueryObj = QueryObj>(key: QueryTerm["q"][0], matches: string[], opts?: Partial<MatchMultipleOpts<string>>) => QueryTerm<T>;
|
|
34
|
+
/**
|
|
35
|
+
* Returns a {@link QueryTerm} for use with {@link query} to perform set-like
|
|
36
|
+
* intersection or union queries with optional negation/exclusions. Matches set
|
|
37
|
+
* of given values against an item's chosen field of values and by default only
|
|
38
|
+
* succeeds if all provided `includes` can be matched (aka intersection query)
|
|
39
|
+
* and there're no values from `excludes` present.
|
|
40
|
+
*
|
|
41
|
+
* @remarks
|
|
42
|
+
* If the `union` option is true, only one of the provided values needs to
|
|
43
|
+
* match. Exclusions _always_ take precedence.
|
|
44
|
+
*
|
|
45
|
+
* Note: See {@link matchStrings} for a syntax sugar of this function, aimed at
|
|
46
|
+
* matching `string[]` options.
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* ```ts
|
|
50
|
+
* const DB = [
|
|
51
|
+
* { id: 1, tags: ["a", "b"] },
|
|
52
|
+
* { id: 2, tags: ["c", "b"] },
|
|
53
|
+
* { id: 3, tags: ["c", "a"] },
|
|
54
|
+
* ];
|
|
55
|
+
*
|
|
56
|
+
* // tag intersection
|
|
57
|
+
* query(DB, [matchStrings("tags", ["a", "b"])])
|
|
58
|
+
* // [ { id: 1, tags: ["a", "b"] } ]
|
|
59
|
+
*
|
|
60
|
+
* // tag union
|
|
61
|
+
* query(DB, [matchStrings("tags", ["a", "b"])])
|
|
62
|
+
* // here returns full DB...
|
|
63
|
+
* // since each item either has `a` and/or `b` tags
|
|
64
|
+
*
|
|
65
|
+
* // tag exclusion (require `a`, disallow `b`)
|
|
66
|
+
* query(DB, [matchStrings("tags", ["a", "!b"])])
|
|
67
|
+
* // [ { id: 3, tags: ["c", "a"] } ]
|
|
68
|
+
* ```
|
|
69
|
+
*
|
|
70
|
+
* @param key
|
|
71
|
+
* @param matches
|
|
72
|
+
* @param opts
|
|
73
|
+
*/
|
|
74
|
+
export declare const matchMultiple: <T extends QueryObj = QueryObj, V = any>(key: QueryTerm["q"][0], includes: V[], excludes: V[], opts?: Partial<MatchMultipleOpts<V>> | undefined) => QueryTerm<T>;
|
|
75
|
+
/**
|
|
76
|
+
* Returns a {@link QueryTerm} to match a key's value against a regexp or string
|
|
77
|
+
* expression.
|
|
78
|
+
*
|
|
79
|
+
* @remarks
|
|
80
|
+
* If `expr` is a regexp it will be used as is, but if given a string the
|
|
81
|
+
* following rules apply:
|
|
82
|
+
*
|
|
83
|
+
* - if `expr` is the sole `*` it will match any non-null value
|
|
84
|
+
* - if `expr` starts with `=`, `!=`, `<`, `<=`, `>=` or `>`, values will be
|
|
85
|
+
* matched using comparators. If the following chars in `expr` are numeric,
|
|
86
|
+
* the comparisons will be done as numbers otherwise as strings. Whitespace
|
|
87
|
+
* between operator and value are ignored.
|
|
88
|
+
*
|
|
89
|
+
* @example
|
|
90
|
+
* ```ts
|
|
91
|
+
* const DB = [
|
|
92
|
+
* { id: "aaa", score: 32 },
|
|
93
|
+
* { id: "bbbb", score: 60 },
|
|
94
|
+
* { id: "c", score: 15 },
|
|
95
|
+
* ];
|
|
96
|
+
*
|
|
97
|
+
* query(DB, [matchPattern("id", /[a-z]{4,}/)]);
|
|
98
|
+
* // [{ id: "bbbb", score: 60 }]
|
|
99
|
+
* query(DB, [matchPattern("id", ">= c")]);
|
|
100
|
+
* // [{ id: "c", score: 15 }]
|
|
101
|
+
*
|
|
102
|
+
* query(DB, [matchPattern("score", "<50")]);
|
|
103
|
+
* // [{ id: "a", score: 32 }, { id: "c", score: 15 }]
|
|
104
|
+
* ```
|
|
105
|
+
*
|
|
106
|
+
* @param key
|
|
107
|
+
* @param expr
|
|
108
|
+
* @param opts
|
|
109
|
+
*/
|
|
110
|
+
export declare const matchPattern: <T extends QueryObj = QueryObj>(key: QueryTerm["q"][0], expr: string | RegExp, opts?: Partial<QueryOpts>) => QueryTerm<T>;
|
|
111
|
+
/**
|
|
112
|
+
* Same as the comparison expression case of {@link matchPattern}, only
|
|
113
|
+
* accepting different args.
|
|
114
|
+
*
|
|
115
|
+
* @param key
|
|
116
|
+
* @param match
|
|
117
|
+
* @param opts
|
|
118
|
+
*/
|
|
119
|
+
export declare const matchCompare: <T extends QueryObj = QueryObj>(key: QueryTerm["q"][0], op: Operator | Predicate<any>, arg: NumOrString, opts?: Partial<QueryOpts>) => QueryTerm<T>;
|
|
120
|
+
//# sourceMappingURL=match.d.ts.map
|
package/match.js
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { isNumber } from "@thi.ng/checks/is-number";
|
|
2
|
+
import { isString } from "@thi.ng/checks/is-string";
|
|
3
|
+
import { numericOp, stringOp } from "@thi.ng/compare";
|
|
4
|
+
/**
|
|
5
|
+
* Syntax sugar for {@link matchMultiple} using string arrays. Matches set of
|
|
6
|
+
* given strings against an item's chosen field of strings and by default only
|
|
7
|
+
* succeeds if all provided strings can be matched.
|
|
8
|
+
*
|
|
9
|
+
* @remarks
|
|
10
|
+
* Any provided string prefixed with `!` is treated as an exclusion and any item
|
|
11
|
+
* which contains any of these exclusions is automatically rejected, even if
|
|
12
|
+
* other strings could be matched.
|
|
13
|
+
*
|
|
14
|
+
* See {@link matchMultiple} for more details & code example.
|
|
15
|
+
*
|
|
16
|
+
* @param key
|
|
17
|
+
* @param matches
|
|
18
|
+
* @param opts
|
|
19
|
+
* @returns
|
|
20
|
+
*/
|
|
21
|
+
export const matchStrings = (key, matches, opts) => {
|
|
22
|
+
const [includes, excludes] = matches.reduce((acc, x) => {
|
|
23
|
+
x[0] === "!" ? acc[1].push(x.substring(1)) : acc[0].push(x);
|
|
24
|
+
return acc;
|
|
25
|
+
}, [[], []]);
|
|
26
|
+
return matchMultiple(key, includes, excludes, opts);
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Returns a {@link QueryTerm} for use with {@link query} to perform set-like
|
|
30
|
+
* intersection or union queries with optional negation/exclusions. Matches set
|
|
31
|
+
* of given values against an item's chosen field of values and by default only
|
|
32
|
+
* succeeds if all provided `includes` can be matched (aka intersection query)
|
|
33
|
+
* and there're no values from `excludes` present.
|
|
34
|
+
*
|
|
35
|
+
* @remarks
|
|
36
|
+
* If the `union` option is true, only one of the provided values needs to
|
|
37
|
+
* match. Exclusions _always_ take precedence.
|
|
38
|
+
*
|
|
39
|
+
* Note: See {@link matchStrings} for a syntax sugar of this function, aimed at
|
|
40
|
+
* matching `string[]` options.
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* ```ts
|
|
44
|
+
* const DB = [
|
|
45
|
+
* { id: 1, tags: ["a", "b"] },
|
|
46
|
+
* { id: 2, tags: ["c", "b"] },
|
|
47
|
+
* { id: 3, tags: ["c", "a"] },
|
|
48
|
+
* ];
|
|
49
|
+
*
|
|
50
|
+
* // tag intersection
|
|
51
|
+
* query(DB, [matchStrings("tags", ["a", "b"])])
|
|
52
|
+
* // [ { id: 1, tags: ["a", "b"] } ]
|
|
53
|
+
*
|
|
54
|
+
* // tag union
|
|
55
|
+
* query(DB, [matchStrings("tags", ["a", "b"])])
|
|
56
|
+
* // here returns full DB...
|
|
57
|
+
* // since each item either has `a` and/or `b` tags
|
|
58
|
+
*
|
|
59
|
+
* // tag exclusion (require `a`, disallow `b`)
|
|
60
|
+
* query(DB, [matchStrings("tags", ["a", "!b"])])
|
|
61
|
+
* // [ { id: 3, tags: ["c", "a"] } ]
|
|
62
|
+
* ```
|
|
63
|
+
*
|
|
64
|
+
* @param key
|
|
65
|
+
* @param matches
|
|
66
|
+
* @param opts
|
|
67
|
+
*/
|
|
68
|
+
export const matchMultiple = (key, includes, excludes, opts) => {
|
|
69
|
+
const { union, value: valueFn } = { union: false, ...opts };
|
|
70
|
+
return excludes.length
|
|
71
|
+
? {
|
|
72
|
+
q: [
|
|
73
|
+
key,
|
|
74
|
+
(values) => {
|
|
75
|
+
const $values = valueFn ? valueFn(values) : values;
|
|
76
|
+
for (let x of excludes) {
|
|
77
|
+
if ($values.includes(x))
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
let match = false;
|
|
81
|
+
for (let x of includes) {
|
|
82
|
+
if ($values.includes(x)) {
|
|
83
|
+
match = true;
|
|
84
|
+
if (union)
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
87
|
+
else if (!union) {
|
|
88
|
+
match = false;
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return match;
|
|
93
|
+
},
|
|
94
|
+
],
|
|
95
|
+
opts: { cwise: false },
|
|
96
|
+
}
|
|
97
|
+
: { q: [key, includes], opts: { intersect: !union } };
|
|
98
|
+
};
|
|
99
|
+
/**
|
|
100
|
+
* Returns a {@link QueryTerm} to match a key's value against a regexp or string
|
|
101
|
+
* expression.
|
|
102
|
+
*
|
|
103
|
+
* @remarks
|
|
104
|
+
* If `expr` is a regexp it will be used as is, but if given a string the
|
|
105
|
+
* following rules apply:
|
|
106
|
+
*
|
|
107
|
+
* - if `expr` is the sole `*` it will match any non-null value
|
|
108
|
+
* - if `expr` starts with `=`, `!=`, `<`, `<=`, `>=` or `>`, values will be
|
|
109
|
+
* matched using comparators. If the following chars in `expr` are numeric,
|
|
110
|
+
* the comparisons will be done as numbers otherwise as strings. Whitespace
|
|
111
|
+
* between operator and value are ignored.
|
|
112
|
+
*
|
|
113
|
+
* @example
|
|
114
|
+
* ```ts
|
|
115
|
+
* const DB = [
|
|
116
|
+
* { id: "aaa", score: 32 },
|
|
117
|
+
* { id: "bbbb", score: 60 },
|
|
118
|
+
* { id: "c", score: 15 },
|
|
119
|
+
* ];
|
|
120
|
+
*
|
|
121
|
+
* query(DB, [matchPattern("id", /[a-z]{4,}/)]);
|
|
122
|
+
* // [{ id: "bbbb", score: 60 }]
|
|
123
|
+
* query(DB, [matchPattern("id", ">= c")]);
|
|
124
|
+
* // [{ id: "c", score: 15 }]
|
|
125
|
+
*
|
|
126
|
+
* query(DB, [matchPattern("score", "<50")]);
|
|
127
|
+
* // [{ id: "a", score: 32 }, { id: "c", score: 15 }]
|
|
128
|
+
* ```
|
|
129
|
+
*
|
|
130
|
+
* @param key
|
|
131
|
+
* @param expr
|
|
132
|
+
* @param opts
|
|
133
|
+
*/
|
|
134
|
+
export const matchPattern = (key, expr, opts) => {
|
|
135
|
+
let re;
|
|
136
|
+
if (expr instanceof RegExp) {
|
|
137
|
+
re = expr;
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
if (expr === "*")
|
|
141
|
+
return { q: [key, (x) => x != null], opts };
|
|
142
|
+
if (/^[<>=!]/.test(expr)) {
|
|
143
|
+
const op = /^[<>=!]+/.exec(expr)[0];
|
|
144
|
+
const arg = expr.substring(op.length).trim();
|
|
145
|
+
const argN = parseFloat(arg);
|
|
146
|
+
return matchCompare(key, op, isNaN(argN) ? arg : argN, opts);
|
|
147
|
+
}
|
|
148
|
+
re = new RegExp(expr, "i");
|
|
149
|
+
}
|
|
150
|
+
return {
|
|
151
|
+
q: [
|
|
152
|
+
key,
|
|
153
|
+
(x) => (isString(x) || isNumber(x)) && re.test(String(x)),
|
|
154
|
+
],
|
|
155
|
+
opts,
|
|
156
|
+
};
|
|
157
|
+
};
|
|
158
|
+
/**
|
|
159
|
+
* Same as the comparison expression case of {@link matchPattern}, only
|
|
160
|
+
* accepting different args.
|
|
161
|
+
*
|
|
162
|
+
* @param key
|
|
163
|
+
* @param match
|
|
164
|
+
* @param opts
|
|
165
|
+
*/
|
|
166
|
+
export const matchCompare = (key, op, arg, opts) => ({
|
|
167
|
+
q: [key, isNumber(arg) ? numericOp(op, arg) : stringOp(op, arg)],
|
|
168
|
+
opts,
|
|
169
|
+
});
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thi.ng/oquery",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"description": "Datalog-inspired, optimized pattern/predicate query engine for JS objects & arrays",
|
|
3
|
+
"version": "2.2.0",
|
|
4
|
+
"description": "Datalog-inspired, optimized pattern/predicate query engine for JS objects & arrays of objects",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"module": "./index.js",
|
|
7
7
|
"typings": "./index.d.ts",
|
|
@@ -36,7 +36,8 @@
|
|
|
36
36
|
"dependencies": {
|
|
37
37
|
"@thi.ng/api": "^8.9.5",
|
|
38
38
|
"@thi.ng/checks": "^3.4.5",
|
|
39
|
-
"@thi.ng/
|
|
39
|
+
"@thi.ng/compare": "^2.2.0",
|
|
40
|
+
"@thi.ng/defmulti": "^3.0.0",
|
|
40
41
|
"@thi.ng/equiv": "^2.1.30"
|
|
41
42
|
},
|
|
42
43
|
"devDependencies": {
|
|
@@ -78,6 +79,9 @@
|
|
|
78
79
|
"./api": {
|
|
79
80
|
"default": "./api.js"
|
|
80
81
|
},
|
|
82
|
+
"./match": {
|
|
83
|
+
"default": "./match.js"
|
|
84
|
+
},
|
|
81
85
|
"./query": {
|
|
82
86
|
"default": "./query.js"
|
|
83
87
|
}
|
|
@@ -91,5 +95,5 @@
|
|
|
91
95
|
],
|
|
92
96
|
"year": 2020
|
|
93
97
|
},
|
|
94
|
-
"gitHead": "
|
|
98
|
+
"gitHead": "b2ef5a1b8932d067af4ec2fc7da03d59d6868dc7\n"
|
|
95
99
|
}
|
package/query.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { Nullable } from "@thi.ng/api";
|
|
2
|
+
import type { KeyQueryFn, KeyQueryOpts, MultiQueryOpts, QueryFn, QueryObj, QueryOpts, QueryTerm } from "./api.js";
|
|
2
3
|
/**
|
|
3
4
|
* Generic Higher-order function to return an actual query function based on
|
|
4
5
|
* given behavior options.
|
|
@@ -25,4 +26,21 @@ export declare const defQuery: <T extends QueryObj | QueryObj[] = QueryObj>(opts
|
|
|
25
26
|
* @param opts -
|
|
26
27
|
*/
|
|
27
28
|
export declare const defKeyQuery: <T extends QueryObj | QueryObj[] = QueryObj>(opts?: Partial<KeyQueryOpts>) => KeyQueryFn<T>;
|
|
29
|
+
/**
|
|
30
|
+
* Multi-term query function for collections (arrays) of {@link QueryObj}ects.
|
|
31
|
+
* Takes a number of {@link QueryTerm}s and matches each term in succession
|
|
32
|
+
* against the array of results of the previous terms (i.e. each sub-query is
|
|
33
|
+
* potentially further narrowing the result set). Returns final results,
|
|
34
|
+
* possibly post-processed, depending on given options.
|
|
35
|
+
*
|
|
36
|
+
* @remarks
|
|
37
|
+
* Each {@link QueryTerm} can provide its own options and post-processing
|
|
38
|
+
* function. Furthermore, global post-processing (e.g. limiting number of final
|
|
39
|
+
* results, sorting by key) can be configured via `opts`.
|
|
40
|
+
*
|
|
41
|
+
* @param db
|
|
42
|
+
* @param terms
|
|
43
|
+
* @param opts
|
|
44
|
+
*/
|
|
45
|
+
export declare const query: <T extends QueryObj = QueryObj>(db: T[], terms: Nullable<QueryTerm<T>>[], opts?: Partial<MultiQueryOpts<T>>) => T[];
|
|
28
46
|
//# sourceMappingURL=query.d.ts.map
|
package/query.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { isArray } from "@thi.ng/checks/is-array";
|
|
2
2
|
import { isFunction } from "@thi.ng/checks/is-function";
|
|
3
3
|
import { isSet } from "@thi.ng/checks/is-set";
|
|
4
|
+
import { compare } from "@thi.ng/compare/compare";
|
|
5
|
+
import { compareByKey } from "@thi.ng/compare/keys";
|
|
6
|
+
import { reverse } from "@thi.ng/compare/reverse";
|
|
4
7
|
import { defmulti } from "@thi.ng/defmulti/defmulti";
|
|
5
8
|
import { equiv } from "@thi.ng/equiv";
|
|
6
9
|
/**
|
|
@@ -394,3 +397,37 @@ export const defKeyQuery = (opts) => {
|
|
|
394
397
|
}
|
|
395
398
|
});
|
|
396
399
|
};
|
|
400
|
+
/**
|
|
401
|
+
* Multi-term query function for collections (arrays) of {@link QueryObj}ects.
|
|
402
|
+
* Takes a number of {@link QueryTerm}s and matches each term in succession
|
|
403
|
+
* against the array of results of the previous terms (i.e. each sub-query is
|
|
404
|
+
* potentially further narrowing the result set). Returns final results,
|
|
405
|
+
* possibly post-processed, depending on given options.
|
|
406
|
+
*
|
|
407
|
+
* @remarks
|
|
408
|
+
* Each {@link QueryTerm} can provide its own options and post-processing
|
|
409
|
+
* function. Furthermore, global post-processing (e.g. limiting number of final
|
|
410
|
+
* results, sorting by key) can be configured via `opts`.
|
|
411
|
+
*
|
|
412
|
+
* @param db
|
|
413
|
+
* @param terms
|
|
414
|
+
* @param opts
|
|
415
|
+
*/
|
|
416
|
+
export const query = (db, terms, opts = {}) => {
|
|
417
|
+
for (let term of terms) {
|
|
418
|
+
if (!term)
|
|
419
|
+
continue;
|
|
420
|
+
db = (defQuery(term.opts)(db, term.q[0], term.q[1]));
|
|
421
|
+
term.post && (db = term.post(db));
|
|
422
|
+
if (!db.length)
|
|
423
|
+
return db;
|
|
424
|
+
}
|
|
425
|
+
const limit = opts.limit || 0;
|
|
426
|
+
if (limit > 0 && limit < db.length) {
|
|
427
|
+
db.length = limit;
|
|
428
|
+
}
|
|
429
|
+
if (opts.sort) {
|
|
430
|
+
db.sort(compareByKey(opts.sort, opts.reverse ? reverse(compare) : compare));
|
|
431
|
+
}
|
|
432
|
+
return db;
|
|
433
|
+
};
|