@thi.ng/oquery 2.1.47 → 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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # Change Log
2
2
 
3
- - **Last updated**: 2023-08-27T11:20:59Z
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 package provides a single higher-order function `defQuery()`, which takes a
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.15 KB
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 query = defQuery({ partial: true });
160
+ const q = defQuery({ partial: true });
156
161
 
157
- console.log(query(DB, null, "knows", "bob"));
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
- query(DB, "alice", "knows", null)
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
- query(DB, (s) => s[0] === "a", "knows", "charlie")
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
- const DBALT = [
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 query = defQuery({ partial: true });
239
+ const q = defQuery({ partial: true });
233
240
 
234
241
  // find all subjects with `type = "person"` relationship
235
- query(DB, null, "type", "person");
242
+ q(DB, null, "type", "person");
236
243
  // { alice: { type: 'person' }, bob: { type: 'person' } }
237
244
 
238
245
  // everyone w/ given min age
239
- query(DB, null, "age", (age) => age >= 33)
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
- query(DB, (id) => id >= "a" && id < "c", null, null)
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, IObjectOf, NumOrString, Predicate, Predicate2 } from "@thi.ng/api";
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 = IObjectOf<any>;
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?: QueryObj[]) => QueryObj[];
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 false (default), an array or Set
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). If true, ALL of the query elements
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
@@ -1,3 +1,4 @@
1
1
  export * from "./api.js";
2
+ export * from "./match.js";
2
3
  export * from "./query.js";
3
4
  //# sourceMappingURL=index.d.ts.map
package/index.js CHANGED
@@ -1,2 +1,3 @@
1
1
  export * from "./api.js";
2
+ export * from "./match.js";
2
3
  export * from "./query.js";
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.1.47",
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/defmulti": "^2.1.45",
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": "1bbef970bab7f7def0700e587cf0471e1e1ef9f3\n"
98
+ "gitHead": "b2ef5a1b8932d067af4ec2fc7da03d59d6868dc7\n"
95
99
  }
package/query.d.ts CHANGED
@@ -1,4 +1,5 @@
1
- import type { KeyQueryFn, KeyQueryOpts, QueryFn, QueryObj, QueryOpts } from "./api.js";
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
+ };